Perl 定义或在列表上下文中,为什么是标量?

Perl defined-or in a list context, why a scalar?

use strict;
use warnings;
use Test::More;

subtest 'explicit array' => sub  {
    my @row = (1,2,3);
    # let's disassamble the array.
    # without default it works:
    my ($one, $two, $three) =  @row;
    is($one, 1, 'one');
    # this works too:
    ($one, $two, $three) =  @row ? @row : (10,20,30);
    is($one, 1, 'one');
    # and the default hits
    my @emptyness;
    ($one, $two, $three) = @emptyness ? @emptyness : (10,20,30);
    is($one, 10, 'one default');
    # however, this squashes the array to a scalar
    ($one, $two, $three) =  @row // (10,20,30);
    is($one, 3, 'scalar, length');
    is($two, undef, 'nothing else');
    # shouldn't 'defined-or' be equivalent to a ternary with a check against undef?
    # ($one, $two, $three) = defined @emptyness ? @emptyness : (10,20,30); # fails!
    # "Can't use 'defined(@array)' (Maybe you should just omit the defined()?)"
    # Probably @array // ... should fail in the same way, but instead it returns @array
    # in a scalar context.
    # so maybe this is a bug
};


done_testing();

或者有人能给我一个合理的解释吗?

您观察到的行为是预期行为。这记录在 perlop, in the section Logical Defined-Or:

EXPR1 // EXPR2 returns the value of EXPR1 if it's defined, otherwise, the value of EXPR2 is returned. (EXPR1 is evaluated in scalar context, EXPR2 in the context of // itself).

并且,perldoc 稍后提供了以下示例:

In particular, this means that you shouldn't use this for selecting between two aggregates for assignment:

@a = @b || @c;            # This doesn't do the right thing
@a = scalar(@b) || @c;    # because it really means this.
@a = @b ? @b : @c;        # This works fine, though.

defined(@array) 有点奇怪,Perl 正试图摆脱它。来自 perldoc -f defined:

Use of "defined" on aggregates (hashes and arrays) is no longer supported. It used to report whether memory for that aggregate had ever been allocated.

这不是在测试它当前是否有任何元素!要检查大小,只需在标量上下文中使用数组变量(例如条件):

if( @array ) { ... }

而且,达达已经解释过,这给它添加了它自己的曲折。

// 必须在标量上下文中评估其左侧运算符,因为它需要标量来评估定义性。

@a returns 数组在标量上下文中包含的元素数。

所以 @a // ... 总是 returns @a 中的元素数(因为所有数字都是定义值)。


没有为标量序列(“列表”)作为一个整体定义定义性或真实性的概念。它们仅适用于单个标量。因此,//&&and||or!not?:需要来自最左侧操作数的标量,因此他们在标量上下文中对其进行评估。 xor 需要测试其两个操作数的真实性,因此在标量上下文中对其进行评估。

$ perl -M5.010 -e'
   sub cx { print wantarray ? " list  " : " scalar"; $_[0] }

   print "// ";  @a =   cx($u) //  cx();     say "";
   print "&& ";  @a =   cx(0)  &&  cx();     say "";
   print "and";  @a = ( cx(1)  and cx() );   say "";
   print "|| ";  @a =   cx(0)  ||  cx();     say "";
   print "or ";  @a = ( cx(0)  or  cx() );   say "";
   print "xor";  @a = ( cx(0)  xor cx(0) );  say "";
   print "!  ";  @a =   !   cx();            say "";
   print "not";  @a =   not cx();            say "";
   print "?: ";  @a =  cx() ? 0    : 0;
                 @a =  1    ? cx() : 0;
                 @a =  0    ? 0    : cx();   say "";
'
//  scalar list
&&  scalar
and scalar list
||  scalar list
or  scalar list
xor scalar scalar
!   scalar
not scalar
?:  scalar list   list

您可能误以为 @a 总是 return 是其元素的列表,并且当在标量上下文中计算时,这会被强制计入计数。但事实并非如此。

根本就没有列表这样的东西。当我们说“评估一个列表”或“returns 一个列表”时,我们只是指“向堆栈添加零个或多个标量”。 return 一个标量和 returning “一个标量的列表”之间绝对没有区别。两者都是指将标量添加到堆栈。

由于没有 returned 的列表数据结构,因此在标量上下文中没有任何东西可以神奇地强制转换为标量;在标量上下文中评估时,每个运算符都可以 return 单个标量。这允许每个操作员在标量上下文中选择他们想要 return 的内容。 It varies greatly.

简而言之,@a return 是标量上下文中的元素数,而不是它包含的元素,就像它在列表上下文中那样。

         +-------------------------- (Scalar context) Returns 3
         |            +------------- (List context) Returns three scalars
         |            |
      vvvv     vvvvvvvv
() =  @row // (10,20,30);
         +-------------------------- (Scalar context) Returns 3
         |      +------------------- (List context) Returns three scalars
         |      |           +------- (List context) Returns three scalars
         |      |           |
      vvvv   vvvv    vvvvvvvv
() =  @row ? @row : (10,20,30);

最后我们来分析一下@row // ....

我们已经确定 @row 是在上面的标量上下文中计算的,并且它 return 是数组中元素的数量 in array 。

嗯,数值不一定是undef,所以都是定义的。所以这意味着 @row // ... 右边的大小永远不会被评估。你还不如写 scalar(@row).

//|| 首先在其左侧参数上施加标量上下文。 // 检查是否定义了该参数。 || 检查该参数是否“不为假”。 false 在 Perl 中是任何值 undef、0、“0”或“”。

数组的标量值始终是已定义的,因此您不能像以前那样在检查中使用它。 @row // (10,20,30)defined(scalar(@row)) or (10,20,30) 相同。

有关上下文的更详细讨论,请参阅 Want 模块的文档。