Home > Enterprise >  Understanding the difference in behaviour between perl's assign-or operator and the combination
Understanding the difference in behaviour between perl's assign-or operator and the combination

Time:11-25

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 allows f() || 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 after f returns.

Let's look at $h{x} ||= f(\%h);.

From the above, we conclude the following:

  • $h{x} must be evaluated before the call to f.
  • 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.

  • Related