2

今天,当我在 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

相反,给定相同的设置,以下语句的行为不同。

$h{x} = $h{x} || f(\%h); # $h{x} is now 42

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

如果这是由于自动激活,是autovivification模块中的错误或缺失功能似乎无法检测到此特定构造中的自动激活吗?

4

2 回答 2

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}给一个 sub,Perl 实际上传递一个神奇的标量来$h{x}代替它。如果可能,这样做是为了防止复活。它并不便宜,但它避免了很多令人讨厌的惊喜。

Perl 可以$h{x} ||= f(\%h);使用相同的机制来延迟激活,但这不值得。

于 2021-11-24T18:50:22.710 回答
2

关键是仅仅检查一个键并不能自动激活它

perl -Mstrict -wE'my %h; if ($h{k}) { say "yes" }; say "Exists" if exists $h{k}'

什么都不打印;密钥k尚未添加到哈希中。(自动激活需要更多,比如测试嵌套键——然后必须创建通向最后一个键的键。)

这解释了这两种情况的不同之处:||=当sub运行时$h{x}分配†</sup> ,因此必须首先为该分配创建键,而在另一种情况下,仅在 sub运行之前检查,所以不会发生自动存活。fx$h{x}f

我不知道实际的实现,||=也没有在文档中找到任何具体的声明。


†</sup>||=列为赋值运算符

于 2021-11-24T18:24:22.997 回答