Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Using ternary operator as lvalue in push

by oha (Friar)
on Jul 31, 2007 at 15:22 UTC ( #629852=perlquestion: print w/replies, xml ) Need Help??

oha has asked for the wisdom of the Perl Monks concerning the following question:

In another node i was replying and i wrote a piece of code which was not working. after some changes i made it work but i didn't figured out why wasn't. after some meditation i came up asking a friend, and seems like what didn't worked for me, worked for him.

i knew (but i checked perlop) that the ternary operator can be used as an lvalue (e.g.: $cond ? $a : $b = 0) and i expected it had worked on push too:

push $cond>0 ? @a : @b , $elem;
but the compiler complains (complaier?):
Type of arg 1 to push must be array (not null operation) at ...

push @{$cond>0 ? \@a : \@b}, $elem;
works as expected.

since my friend is serious enough, have i to think it works in other version of perl? (mine 5.8.4)
what exactly the compiler means with "null operation"?

PS: that's what i like of perl and perlmonks, trying to answer someone question i got a brand new one. :)
Oha

Replies are listed 'Best First'.
Re: Using ternary operator as lvalue in push
by Roy Johnson (Monsignor) on Jul 31, 2007 at 16:39 UTC
    push is effectively prototyped, so it needs to see that array sigil on its first operand.

    Caution: Contents may have been coded under pressure.
      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.
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.

      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
        Compile time constant removal magic.

        perl -MO=Deparse play produces:

        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

      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

        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";
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.

      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;.

      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.

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.
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

      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.
        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
      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.
        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 @_:

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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://629852]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others chilling in the Monastery: (3)
As of 2022-05-17 02:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (65 votes). Check out past polls.

    Notices?