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
Re: ||= oddity
by ikegami (Patriarch) 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.
| [reply] [d/l] [select] |
|
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! | [reply] [d/l] |
|
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)? | [reply] [d/l] [select] |
|
... 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...
| [reply] [d/l] [select] |
|
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.
| [reply] [d/l] [select] |
|
|
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.
| [reply] [d/l] [select] |
|
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'
| [reply] [d/l] |
|
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.
| [reply] |
|
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.
| [reply] [d/l] [select] |
|
| [reply] |
Re: ||= oddity
by FunkyMonk (Chancellor) 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
| [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
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
| [reply] [d/l] [select] |
|
Cool. I just learned how to do a Google code search.
| [reply] |
|
| [reply] |
|
|
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. | [reply] [d/l] [select] |
|
|