Today I was surprised when I came across the following behaviour in perl:
sub f { die if %{ $_[0] }; 42 }
my %h;
$h{x} ||= f(\%h); # we die. $_[0] references a hash with an 'x' key during f's run-time
In contrast, given the same set-up, the following statement behaves differently.
$h{x} = $h{x} || f(\%h); # $h{x} is now 42
Is this potential difference between assign-or and the combination of assignment and logical-or documented somewhere?
If this is due to auto-vivification, is it a bug or missing feature in the autovivification
module which doesn't seem to be able to detect auto-vivification in this particular construct?
CodePudding user response:
Key pieces of info:
||
and||=
are short-circuiting, so they must evaluate their left-hand side before their right-hand side. (This allowsf() || die()
.)=
evaluates its right-hand side operand before its left-hand side. (This allows$x = f($x)
.)The left-hand side of
||=
is only evaluated once.Hash elements aren't vivify (created) on access unless necessary (i.e. in an lvalue context).
Let's look at $h{x} = $h{x} || f(\%h);
.
From the above, we conclude the following:
- Only the left-most
$h{x}
needs to vivify the element. - The left-most
$h{x}
is evaluated afterf
returns.
Let's look at $h{x} ||= f(\%h);
.
From the above, we conclude the following:
$h{x}
must be evaluated before the call tof
.- Evaluating
$h{x}
must produce a modifiable value, so it must vivify$h{x}
.
Lvalue Context
- Left-hand side of assignment operators (incl
||=
). - Operand of pre/post-increment/decrement.
- Operand of
\
. - Foreach loop list expression (because of
$_ = uc($_) for @a;
). - Subroutine arguments (because of
sub ucip { $_[0] = uc($_[0]) }
), but see "One Exception" below. - Others?
One Exception
If you pass $h{x}
to a sub, Perl actually passes a magical scalar which proxies $h{x}
instead. This is done to prevent vivification if possible. It's not cheap, but it avoids a lot of nasty surprises.
It is possible for Perl to delay vivification in $h{x} ||= f(\%h);
using the same mechanism, but it's not worth the expense.
CodePudding user response:
Merely checking on a key doesn't, in fact, autovivify it
perl -Mstrict -wE'my %h; if ($h{k}) { say "yes" }; say "Exists" if exists $h{k}'
Prints nothing; key k
has not been added to the hash. (One needs more for autovivification to kick in, like test nested keys -- then the ones leading up to the last one must be created.)
The two cases differ in this sense, since with ||=
the $h{x}
is being assigned to when the sub f
runs†, and so key x
must be created for the purpose of this asignment, while in the other case $h{x}
is merely being checked before the sub f
runs, so no autovivification happens.
This is how I view this interesting difference -- I don't know the actual implementation of ||=
nor do I know whether there is any specific statement in the docs (other than the footnote).
† We get a bit of a hint in the docs as ||=
is listed as an assignment operator
... they all have the precedence of assignment.