Re: Using ternary operator as lvalue in push
by Roy Johnson (Monsignor) on Jul 31, 2007 at 16:39 UTC
|
| [reply] [Watch: Dir/Any] [d/l] |
|
Is it done this way to allow the prototype to be checked at compile time?
>perl -c -e"sub t(\@) { @{$_[0]} = '!' } t($c?@a:@b); print @b;
Type of arg 1 to main::t must be array (not null operation) at -e line
+ 1, near "@b)"
-e had compilation errors.
| [reply] [Watch: Dir/Any] [d/l] |
Re: Using ternary operator as lvalue in push
by ysth (Canon) on Jul 31, 2007 at 19:03 UTC
|
Did you happen to see what your friend was testing? Or what version he was using? There's been a bug for a long time that something like push CONSTANT-EXPRESSION ? @a : @b, LIST; works when it shouldn't, where CONSTANT-EXPRESSION is a constant or operations that result in a constant, like:
use constant OS => $^O;
push OS eq "linux" ? @a : @b, "tux";
Don't rely on this to continue working: it's clearly a bug. Always use the @{ expression resulting in an arrayref } instead.
Using non-constant expressions before the ?, it fails for me on 5.6.2, 5.8.0, 5.8.8, and 5.9.5. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
ouch... I'm oha's friend :-)
of course, I tested it with:
# This is perl, v5.8.7
my @a = (1,2,3);
my @b = (4,5,6);
push 1==1 ? @a : @b, "foo";
print "a=@a b=@b\n";
I even counter-checked (!) it with:
push 1==0 ? @a : @b, "foo";
to verify that the "foo" was pushed into @b.
of course, such stupid conditionals are optimized away. sorry for fooling myself, oha, and probably others. shame on me :-)
cheers,
Aldo
King of Laziness, Wizard of Impatience, Lord of Hubris
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
my(@a) = (1, 2, 3);
my(@b) = (4, 5, 6);
push @a, 'foo';
print "a=@a b=@b\n";
/msg self Read ALL the post before replying
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
could be, i will ask him.
i used a code that could be optimized by compiler:
my $c = 1;
push $c>0 ? @a : @b, "foo";
maybe different settings would allow the compiler to reduce the condition at compile-time (my friend case) resulting in
push @a, "foo";
that will explain those behaviour
Oha | [reply] [Watch: Dir/Any] [d/l] [select] |
|
my $c = 1;
push $c>0 ? @a : @b, "foo";
is not optimized by the compiler. It only folds constants. $c is not a constant. On the other hand, the following would be optimized:
use constant c => 1;
push c>0 ? @a : @b, "foo";
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Using ternary operator as lvalue in push
by dogz007 (Scribe) on Jul 31, 2007 at 16:11 UTC
|
I've actually run into this problem before. Correct me if I'm wrong, but the code that isn't working for you is:
push $cond > 0 ? @a : @b , $elem;
I remember reading somewhere that the ternary binds more closely than the conditional. This means that the ternary operator sees only the 0, assumes it to be a false condition, and then reports @a back to the conditional statement. @a would then be evaluated in scalar context and compared to $code. No matter what the result, this eventually means that push will be getting a boolean value (which may be null) instead of an array to push into. To fix this, simply enclose the conditional statement in parentheses, as below. This forces the conditional to evaluate first, followed by the ternary, which then reports to push.
push ($cond > 0) ? @a : @b , $elem;
This is what has worked for me in the past. I'm currently on a handicapped (perl-less) machine, so I'm not able to test it. Someone please test and confirm.
Update: This explanation is completely wrong, and does not even work, as explained below. The problem is instead related to the prototyping in push. My deepest apologies. Next time I will wait to consult the texts before answering. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
Youch, that's completely wrong.
First of all, the basic idea is wrong. Relational operators (like >) have higher precedence than the conditional operator (?:), so attempting to raise the precendence of > using parens is a no-go. (See perlop.) *
Furthermore, you actually made things worse by adding the parens because
push ($cond > 0) ? @a : @b, $elem;
is the same as
push($cond > 0) ? @a : @b, $elem;
is the same as
(push($cond > 0)) ? @a : @b, $elem;
which is quite wrong.
Finally, even if done right, the parens don't help. Both
push(($cond > 0) ? @a : @b, $elem);
and
push((($cond > 0) ? @a : @b), $elem);
produce the same result as the OP.
* — You might be thinking of the assignment operators rather than relational operators. Since the conditional operator (?:) has higher precedence than the assignment operators (like =), $cond ? $a = 1 : $b = 1; means ($cond ? $a = 1 : $b) = 1;.
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
I too am on a Perl-less machine, and don't have a lot of experience but here are my two cents:
Won't the following:
push ($cond > 0) ? @a : @b , $elem;
Evaluate the greater than comparison, but then try to push the result before the ternary operator gets a hold of it? The ternary operator, I know, has pretty darn low precedence.
| [reply] [Watch: Dir/Any] [d/l] |
Re: Using ternary operator as lvalue in push
by gam3 (Curate) on Aug 01, 2007 at 14:16 UTC
|
This problem is due to the prototype of the push function.
The prototype for push is \@@ and this means that perl needs to check that the first argument to push is an array.
You can make your own function that will give the same errors with
sub tst_push(\@@) { }
Also note that the code below works, as perl can optimize out the ternary.
push(1 ? @a : @b, 'asdf');
push(1 == 0 ? @a : @b, 'asdf');
The code above make it pretty clear to me that precedence is not the problem.
If you don't like the push(@{$x ? \@a : \@b}.. syntax you could do this:
sub apush($@) {
my $a = shift;
push @$a, @_;
}
and use
apush($cond>0 ? \@a : \@b, $elem);
But to answer the question you pose: the push @{$x ? \@a : \@b}, $elem; code is portable over all perl versions.
-- gam3
A picture is worth a thousand words, but takes 200K.
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Using ternary operator as lvalue in push
by FunkyMonk (Chancellor) on Aug 01, 2007 at 09:34 UTC
|
Having read this thread, I think I would describe this as a case of The Price We Pay For Perl's Magic. The only other place I think of where I've seen something similar, is on a magic print-to-filehandle.
Compare
my $scalar;
print $scalar "sausage";
and
my %hash = ( x => 'XYZ' );
print $hash{x} "sausage";
The first compiles but the second generates a String found where operator expected compile time error (print tells you how to get round this problem, if you're interested).
I think this is a limitation in feature of Perl, and we're just going to have to live with it.
update: oha makes a fair point, and one that I agree with. It was a poor choice of words on my part. Post updated | [reply] [Watch: Dir/Any] [d/l] [select] |
|
It's a hard-wired limitation of Perl 5, but you're not going to have to live with it forever because it's fixed in Perl 6. The way we fixed it was by not committing to scalar/list distinctions until the argument list is actually bound to a function. When we do this, most of the problems of Perl 5's prototypes simply evaporate. In fact, this works fine in pugs:
my @x = my @y = ();
push (False,True).pick ?? @x !! @y, <a b c>;
say "x: @x[]";
say "y: @y[]";
and randomly pushes the list onto one array or the other. | [reply] [Watch: Dir/Any] [d/l] |
|
well, I also expect something like this to work in Perl 6:
($cond ?? @x :: @y).push(<a b c>);
which is a lot more understandable syntax in my opinion.
cheers,
Aldo
King of Laziness, Wizard of Impatience, Lord of Hubris
| [reply] [Watch: Dir/Any] [d/l] |
|
|
I don't think it's a limitation, you can use @{cond?\@a:\@b}. but i would like to spend more words on why push is prototyped: i'll check around.
| [reply] [Watch: Dir/Any] [d/l] |
|
i would like to spend more words on why push is prototyped
So we don't have to pass a reference for the first argument.
If there weren't a prototype, the array being passed in would be flattened:
push @array, $value;
would be equivalent to:
push $array[0], @array[1..$#array], $value;
Let's compare two subroutines with and without prototypes, and see what's in @_:
| [reply] [Watch: Dir/Any] [d/l] [select] |
Re: Using ternary operator as lvalue in push
by DrHyde (Prior) on Aug 01, 2007 at 10:15 UTC
|
I can confirm that without the extra squigglies and stuff it fails on 5.6.2, 5.8.8 and 5.9.5, and that with them it works on all three.
I'm going to blame precedence for now, but B::Deparse isn't helpful to see what's really going on because the compilation error means that perl just stops instead of letting the module get its grubby little paws on the code.
| [reply] [Watch: Dir/Any] |