Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Why are "a ||= b" and "a = a || b" different?

by saintmike (Vicar)
on Mar 03, 2007 at 00:36 UTC ( [id://602998]=perlquestion: print w/replies, xml ) Need Help??

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

I stumbled across
($ret) ||= foo();
today and wondered why foo() is in scalar context. I would have thought that the construct is equivalent to
($ret) = $ret || foo();
in which case foo() inherits the list context from the left side of the assignment.

Ideas?

Replies are listed 'Best First'.
Re: Why are "a ||= b" and "a = a || b" different?
by diotalevi (Canon) on Mar 03, 2007 at 00:57 UTC

    The ||= only gets one shot to evaluate the left side and since it needs a boolean, it does it in scalar context. The context of the right hand side is determined by the left side so foo() is scalar too.

    You really ought to update your node title though. It's misleading. The central question is about context and you'd want to use a title like "Why are "(a) || b" and "(a) = a || b" different."

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      Hmm, not sure I understand the 'one shot' part.

      Wouldn't it be simpler to unravel

      my($ret) ||= foo();
      to
      my($ret) = $ret || foo();
      and process it accordingly? The way it's implemented now is really confusing to the user.
        Hi saintmike,

        diotalevi's perfectly right.

        Look at it this way:

        my($ret) ||= foo();

        What you're doing amounts to this, step-wise:

        1. my ($ret); # Create a scalar variable called $ret 2. $ret ||= foo() # If $ret is undefined or zero, assign it to foo( +)

        But of COURSE $ret is undefined when you first declare it, because you haven't assigned it to anything!

        A similarly bad thing is going on here:

        my($ret) = $ret || foo();

        where you're trying to create a new scalar variable my($ret), but its value depends on a previously defined scalar called $ret (notice that's the same variable name?), or foo() if the the value of the $ret was previously undefined or zero.

        If you use strict and warnings (as you always should), then you'll get an error with the second one.  Try this (essentially equivalent) code:

        use strict; use warnings; my $x = $x || 5; print "Now x = $x\n";

        Without strict and warnings you get:

        Now x = 5

        but with them you get:

        Global symbol "$x" requires explicit package name at test.pl line 5. Execution of test.pl aborted due to compilation errors.

        Finally, the reason your title is misleading (as diotalevi suggests), is because it's perfectly fine to do either:

        use strict; use warnings; my $y = 7; my $x; $x = $x || $y; print "Now x = $x\n";

        and:

        use strict; use warnings; my $y = 7; my $x; $x ||= $y; print "Now x = $x\n";

        which will both yield the expected:

        Now x = 7

        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

        No, it would not be simpler to change (a) ||= foo() to (a) = a || foo. I call your attention to Chapter 10 of On Lisp about pitfalls in macros as reference material and in particular, the part about Number of Evaluations. While you originally wrote (a) as your left hand expression, it could have been something else like bar() or (rand < .5 ? $a : $b). If you executed the left hand side multiple times, you could have inconsistent results.

        # (a) could change entirely if run multiple times ( rand < .5 ? $a : $b ) = ( rand < .5 ? $a : $b ); # (a) might have side effects bar() = bar() || foo(); sub bar :lvalue { ... } # (a) might have side effects tie $a, ...; ($a) = $a || foo();

        In all those cases it would be an error to cause the single mention of (a) to expand to multiple mentions. Internally perl evaluates your (a) once. It uses the expression's value as the input to the || operator and then it uses the same already computed expression as an lvalue. To see this under the hood and why, use the B::Concise module to look at your program's structure. Get Idealized optrees from B::Concise to see a simpler view. I annotated the ouput by hand.

        perl -MO=Concise -e '($a) ||= foo()' | idealized-optree leave enter nextstate orassign # ||= gvsv # << $a sassign # scalar = entersub # foo() pushmark gv # *foo

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

        I think it matters when $ret is a tied variable and there is some sort of side effect when it is read. The ||= version only evaluates it once, I believe. Esoteric? Yes.

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      you'd want to use a title like "Why are "(a) || b" and "(a) = a || b" different."
      I guess you mean

      Why are "(a) ||= b" and "(a) = a || b" different

      and you're right that it's slightly misleading, but I could argue that "a" is a place holder that can hold anything including (a) :).
Re: Why are "a ||= b" and "a = a || b" different?
by varian (Chaplain) on Mar 03, 2007 at 09:50 UTC
    Saintmike, first of all thank you for bringing up this excellent challenge. As the answers of other monks did not seem to provide a concise answer I decided to dig into the issue to learn why. Sure enough Perl must have acted consistent as usual?

    It turns out that your assumption is not correct. In both scenarios foo() result is used in list context. This snippet of code will demonstrate it:

    #!/usr/bin/perl -w use strict; sub foo { return (10,11,12,13); } my $ret = 0; my $ret2= 0; # EXPRESSION A ($ret,$ret2)||= foo(); print "ret=$ret ret2=$ret2\n"; # ret2 returns 13, last element of list # ret remains unused (warning raised) $ret=0; # EXPRESSION B ($ret) = ($ret) || foo(); print "ret=$ret\n"; # returns 10, first element of list

    What drives the whole problem (and solution) is that the '||' operator returns the last value evaluated. And that's where the catch is!
    Expression A evaluates the last element of ($ret,$ret2) against the last element of what foo() returns. Since what is listed on the lefthand of the '=' is part of the expression, only the last element of the left-hand operand gets updated.
    In Expression B first Perl evaluates the expression on the righthand of the '='. The last values of the (righthand) operands are compared and because $ret is zero this option is discarded and the next operand, foo() is taken. So the result of the expression is foo's list (yes, all elements!). Subsequently this result gets assigned to what is on the lefthand of the '='.

    All this shows that it can be tricky to use the '||' operator in a list context. The net result is not obvious and probably one wants to surround any such expression with a little bit of inline documentation so ease code maintenance. Frankly, for myself I would stay away from the construct where possible (except maybe in a nice JAPH).

    Update: 'YAPH'=~s/Y/J/

      To verify whether a function is called in list context or scalar context, use wantarray.

      Here's a snippet of code to verify my assumption:

      my $a; ($a) = ($a) || foo(); ($a) ||= foo(); sub foo { wantarray ? print("list\n") : print("scalar\n"); return 0; }
      and it prints
      list
      scalar
      
      It looks like there really is a difference in the calling context:
      #!/usr/bin/perl use Carp; my $ret; undef $ret; ($ret) ||= foo(); warn "ret is $ret"; undef $ret; ($ret) = $ret || foo(); warn "ret is $ret"; sub foo { carp "Want " . (wantarray ? "array" : "scalar"); (1,2); }
      outputs:
      Want scalar at t3 line 17 main::foo() called at t3 line 8 ret is 2 at t3 line 9. Want array at t3 line 17 main::foo() called at t3 line 12 ret is 1 at t3 line 13.
        void is false
        my $w = wantarray; $w = defined $w ? ($w ? 'list' : 'scalar') : 'void'; carp "wantarray : $w ";
Re: Why are "a ||= b" and "a = a || b" different?
by ambrus (Abbot) on Mar 03, 2007 at 21:07 UTC

    You could just say $ret ||= (foo())[0]; instead.

      The equivalent would actually be

      $ret ||= (foo())[-1];

        I'm affraid no.

        sub foo { wantarray or die "this function is called in list context in all ex +amples"; "alpha", "beta", "gamma"; } $ret = undef; ($ret) = $ret || foo(); print "parenthetical: $ret\n"; $ret = undef; $ret ||= (foo())[0]; print "zero: $ret\n"; $ret = undef; $ret = (foo())[-1]; print "minus one: $ret\n";
        Gives
        parenthetical: alpha zero: alpha minus one: gamma

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2024-04-25 22:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found