了解 Perl 的赋值或运算符与赋值和逻辑或运算符的组合之间的行为差​​异

raf*_*afl 2 perl evaluation operators autovivification

今天,当我在 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
Run Code Online (Sandbox Code Playgroud)

相反,在相同的设置下,以下语句的行为有所不同。

$h{x} = $h{x} || f(\%h); # $h{x} is now 42
Run Code Online (Sandbox Code Playgroud)

分配或与分配和逻辑或的组合之间的潜在差异是否记录在某处?

如果这是由于自动激活造成的,那么模块中是否存在错误或缺少功能autovivification,似乎无法检测到此特定构造中的自动激活?

ike*_*ami 6

关键信息:

  • ||并且||=是短路的,因此它们必须先评估左侧,然后再评估右侧。(这允许f() || die()。)

  • =先评估其右侧操作数,然后再评估左侧操作数。(这允许$x = f($x)。)

  • 的左侧||=仅计算一次。

  • 除非必要(即在左值上下文中),否则哈希元素不会在访问时生动化(创建)。


我们来看一下$h{x} = $h{x} || f(\%h);

综上所述,我们得出以下结论:

  • 只有最左边的元素$h{x}需要使元素生动起来。
  • $h{x}返回后评估最左边的值f

我们来看一下$h{x} ||= f(\%h);

综上所述,我们得出以下结论:

  • $h{x}必须在调用之前进行评估f
  • 评估$h{x}必须产生可修改的值,因此它必须生动$h{x}

左值上下文

  • 赋值运算符的左侧(包括||=)。
  • 前/后递增/递减操作数。
  • 的操作数\
  • Foreach 循环列表表达式(因为$_ = uc($_) for @a;)。
  • 子例程参数(因为sub ucip { $_[0] = uc($_[0]) }),但请参阅下面的“一个例外”。
  • 其他的?

一个例外

如果你传递$h{x}给一个子进程,Perl 实际上会传递一个神奇的标量来代理$h{x}。这样做是为了尽可能防止复活。它并不便宜,但它避免了很多令人讨厌的意外。

Perl 有可能$h{x} ||= f(\%h);使用相同的机制来延迟激活,但不值得为此付出代价。