Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

||= oddity

by throop (Chaplain)
on May 28, 2008 at 20:00 UTC ( #688915=perlquestion: print w/ replies, xml ) Need Help??
throop has asked for the wisdom of the Perl Monks concerning the following question:

Why does this work
$foo ||= [1,2,3]
($foo gets set to 1,2,3 if and only if $foo is false)

But this doesn't

@bar ||= (1,2,3) Can't modify array dereference in logical or assignment (||=) at (eval + 27)[/usr/lib/perl5/5.8.8/perl5db.pl:628] line 2, at EOF

Comment on ||= oddity
Select or Download Code
Re: ||= oddity
by FunkyMonk (Canon) on May 28, 2008 at 20:08 UTC
    (1,2,3) is evaluated in scalar context, but I cannot explain the error you get. You get a much more meaningful error with warnings enabled: Useless use of a constant in void context

    Unless I state otherwise, all my code runs with strict and warnings
Re: ||= oddity
by pc88mxer (Vicar) on May 28, 2008 at 20:31 UTC
    In order to do what I believe you intend, you'll need to use:
    @bar = (1,2,3) unless @bar;
    What is interesting is that this prints x:
    $bar ||= (1,2,'x'); print $bar, "\n";
    So perhaps the RHS is not being interpreted as a list constructor but as a sequence of expressions whose value is the last expression. That would explain this behavior:
    my ($a, $b); ($a, $b) ||= ('a', 'b', 'c'); # $a unchanged, $b <- 'c' ($a, $b) ||= (1, 2, 3); # $a unchanged, $b unchanged $b = (1, 2, 'x'); # $b <- 'x' $a ||= (3, 4, 5); # $a <- 5
    So it appears that ($a, $b) on the LHS is being interpreted as a sequence (not list constructor) of l-values whose value is the last (l-value) expression. However, in this case:
    ($a, $b) = (7,8,9); # $a <- 7, $b <- 8
    we have list constructors on both the LHS and RHS.

      A list is a sequence of expressions. How context propagates into that list has been the subject of much discussion. Here's a conversation piece:

      sub wa { my $place = shift @_; my $context = wantarray ? 'list' : defined wantarray ? 'scalar' : 'void'; warn "'$context' context at '$place'\n"; } my $x ||= (wa('oe1'),wa('oe2'),wa('oe3')); print "\n"; my $y = (wa('e1'),wa('e2'),wa('e3')); print "\n"; my $z = sub { (wa('s1'),wa('s2'),wa('s3')) }->(); __END__ 'void' context at 'oe1' 'void' context at 'oe2' 'scalar' context at 'oe3' 'void' context at 'e1' 'void' context at 'e2' 'scalar' context at 'e3' 'scalar' context at 's1' 'scalar' context at 's2' 'scalar' context at 's3'
        That is surprising. I would have expected the interpreter to be smarter about the list context and skip over evaluating all but the last item. That seems like a obvious optimization opportunity.

        Hmmm... Then again, I probably wouldn't put 3 sub calls in a list if I only meant to evaluate and assign the last.

        Interesting. It appears to be the result of compile-time optimization. (Update: Or maybe it's to generate the "useless use of a constant in void context" warning.)

        sub wa { my $place = shift @_; my $context = wantarray ? 'list' : defined wantarray ? 'scalar' : 'void'; warn "'$context' context at '$place'\n"; } my $x = (wa('a1'),wa('a2'),wa('a3'));

        is compiled to

        >perl -MO=Concise script.pl j <@> leave[1 ref] vKP/REFC ->(end) 1 <0> enter ->2 2 <;> nextstate(main 4 script.pl:8) v ->3 i <2> sassign vKS/2 ->j g <@> list sKP ->h 3 <0> pushmark v ->4 7 <1> entersub[t3] vKS/TARG,1 ->8 ^ | void context - <1> ex-list K ->7 4 <0> pushmark s ->5 5 <$> const[PV "a1"] sM ->6 - <1> ex-rv2cv sK/1 ->- 6 <#> gv[*wa] s ->7 b <1> entersub[t5] vKS/TARG,1 ->c ^ | void context - <1> ex-list K ->b 8 <0> pushmark s ->9 9 <$> const[PV "a2"] sM ->a - <1> ex-rv2cv sK/1 ->- a <#> gv[*wa] s ->b f <1> entersub[t7] sKS/TARG,1 ->g ^ | scalar context - <1> ex-list sK ->f c <0> pushmark s ->d d <$> const[PV "a3"] sM ->e - <1> ex-rv2cv sK/1 ->- e <#> gv[*wa] s ->f h <0> padsv[$x:4,5] sRM*/LVINTRO ->i script.pl syntax OK

        So,
        if the context of the "," operator is known to be scalar at compile-time, the LHS is evaluated in void context.
        if the context of the "," operator is known to be list at compile-time, the LHS is evaluated in list context.
        if the context of the "," operator is not known at compile-time, the LHS is evaluated in the same context as the operator.

      So perhaps the RHS is not being interpreted as a list constructor but as a sequence of expressions....

      If there is such a thing as a "list constructor" in Perl 5 (and that's up for debate), that's all it is -- a sequence of comma-separated expressions.

Re: ||= oddity
by ikegami (Pope) on May 28, 2008 at 20:38 UTC

    @bar can't hold the result of ||= since @bar is an array and the result is a scalar. The LHS of ||= must be a scalar lvalue.

    The following will do what you want:

    @bar = (1,2,3) if !@bar;

    I admit, the error message could be much clearer.

      OK, once I knew what the answer was... it helped me find the answer in a manual. From 'Programming Perl, 2nd Edition', pp 92-93. It lists the assignment operators, including = and ||=, and says
      List assignment may be done only with the plain assignment operator, = +. In a list context, list assignment returns the list of new values +just as scalar assignment does. ...
      Hitting your thumb with a hammer - there's more than one way to do it!
      You may call me stupid, but why can't the scalar result of ||= be assigned to an array?

      This works:

      $ perl -wle 'my @a = 2; print @a' 2 $ perl -wle 'my @a = scalar 2; print @a' 2 $ perl -wle 'my @a = 2+2; print @a' 4

      So we can see that you can assign a scalar to an array, and it does what you mean - it creates an array with one item.

      But why can't you do it with the result of @array ||(1, 2, 3)?

        ... because it's been designed and implemented to operate the way it does — that's all :)

        I think the crucial difference is that the normal assignment operator (=) does not force scalar context upon its LHS (so the array remains an array, not the number of its elements), while ||= does.  It's not so much an issue of not being able to assign a scalar to an array, but rather the problem of the array no longer being (treated like) an array internally...

        If it did work, it would produce useless results.
        Given that (LHS ||= RHS) means (LHS = LHS || RHS),
        Then (@a ||= (1,2,3)) means (@a = @a || (1,2,3)) and thus (@a = $#a+1 || 3).
        Why would you ever want that.

        On the other hand, it would be useful to expand the definition of the ||= operator so that (@a ||= EXPR) means (@a = @a ? @a : EXPR).
        Similarly, it could be useful to expand the definition of &&= such that (@a &&= EXPR) means (@a = @a ? EXPR : ()).
        However, none of **=, +=, *=, &=, <<=, -=, /=, |=, >>=, .=, %=, ^=, //= and x= would be useful for arrays.

Re: ||= oddity
by almut (Canon) on May 28, 2008 at 20:40 UTC

    I'd say the problem is that you're evaluating @bar in scalar context (which gives the number of elements in the array), and that you're then trying to modify that number (which you can't). This is similarly nonsensical as @bar++, or @bar += 5, or @bar |= 1, etc.  All of those assume/force scalar context on the left hand side.

Re: ||= oddity
by moritz (Cardinal) on May 28, 2008 at 20:41 UTC
    $ perl -MO=Deparse -e '@bar ||= (1,2,3)' Can't modify array dereference in logical or assignment (||=) at -e li +ne 1, at EOF -e had compilation errors. @bar ||= ('???', '???', 3); $ perl -MO=Deparse -we '@foo ||= 1' Can't modify array dereference in logical or assignment (||=) at -e li +ne 1, at EOF -e had compilation errors. BEGIN { $^W = 1; } @foo ||= 1;
    So it seems that ||= generally isn't supported in perl.

    Update: ok, other monks explained why. ||= returns a scalar.

    A quick code search shows that all examples "out there" use it only in lines that are commented out, so it seems to be a common mistake actually

      Cool. I just learned how to do a Google code search.
        Yea me too, excellent. *veers off on a tangent* I've often wondered why Google doesn't allow regular expressions, or even most symbols in their regular search. Any ideas?
        ........
        Those are my principles. If you don't like them I have others.
        -- Groucho Marx
        .......
      As an aside: Arrays are treated in the same way when doing smart matching:
      use feature qw(say); say qq(no match) unless (1, 4, 18) ~~ 4; say qq(match) if (1, 4, 18) ~~ 18;
      results in
      no match match
      I tripped over this, too, see http://www.perlmonks.org/?node_id=687969.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (7)
As of 2014-12-29 08:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (185 votes), past polls