为什么山羊运营商有效?

时间:2014-01-10 06:44:56

标签: perl syntax variable-assignment goatse

数组和列表之间以及列表和标量上下文之间的区别已经在去年的Perl社区中进行了相当多的讨论(并且每年都是如此)。我已阅读chromaticfriedo以及this推荐的修士节点的文章。我现在正在尝试理解在perlsecret中记录的goatse算子。

以下是我用来研究它的一些代码:

# right side gets scalar context, so commas return rightmost item
$string = qw(stuff junk things); 
say $string; # things

# right side gets list context, so middle is list assigned in scalar context
$string = () = qw(stuff junk things);
say $string; # 3

# right side gets list context, so creates a list, assigns an item to $string2, and
# evaluates list in scalar context to assign to $string
$string = ($string2) = qw(stuff junk things);
say $string; # 3
say $string2; # stuff

我认为我已经足够了解所有列表和标量上下文工作。标量上下文中的逗号运算符返回其右侧,因此第一个示例只是将逗号表达式中的最后一项(不带任何逗号)分配给$string。在其他示例中,将逗号表达式分配给列表将其放在列表上下文中,因此创建列表,并在标量上下文中评估的列表返回其大小。

有两部分我不明白。

首先,列表应该是不可变的。这是由friedo反复强调的。我想通过=从列表到列表的分配将一个列表中的项目的分配分配给另一个列表中的项目,这就是为什么在第二个示例中$string2得到'stuff',以及为什么我们可以通过列表分配解包@_。但是,我不明白如何将()(空列表)的分配工作。根据我目前的理解,由于列表是不可变的,列表的大小将保持为0,然后在示例2和3中将大小分配给$stuff将使其值为0.列表实际上是不可变的吗?

其次,我已多次读过列表在标量上下文中实际不存在的情况。但是对goatse运算符的解释是它是标量上下文中的列表赋值。这不是列表在标量上下文中不存在的语句的反例吗?或者是其他事情发生在这里?

更新:在理解答案后,我认为额外的一对括号有助于概念化它的工作原理:

$string = ( () = qw(stuff junk things) );

在parens中,=是对'aggregate'的赋值,因此是一个列表赋值运算符(与标量赋值运算符不同,不应与“list context”混淆) ;列表和标量赋值可以在列表或标量上下文中进行)。 ()不会以任何方式改变。 =在Perl中有一个返回值,列表分配的结果通过左$string分配给=。赋值给$string给出了RHS的标量上下文(parens中的所有内容),在标量上下文中,列表赋值运算符的返回值是RHS中的项目数。

您可以将RHS列表分配放入列表上下文中:

($string) = ( () = qw(stuff junk things) );

根据列表上下文中的perlop列表赋值,返回已分配左值的列表,此处为空,因为()中没有任何内容可以分配。所以这里$ string将是undef

3 个答案:

答案 0 :(得分:10)

你误解了。在标量上下文中评估的列表不会得到它们的大小。实际上,在标量上下文中列出一个列表几乎是不可能的。在这里,你有一个带有两个操作数的标量赋值,左边是一个标量变量,右边是一个列表赋值(由标量赋值给定标量上下文)。标量上下文中的列表分配评估为分配的右侧上的项目数。

所以,在:

1 $foo
2 =
3 ()
4 =
5 ('bar')

2,标量赋值,给出1和4个标量上下文。 4,列表赋值,给出3和5列表上下文,但是它本身在标量上下文中并且适当地返回。

(当=是列表赋值或标量赋值完全由周围语法确定;如果左操作数是散列,数组,散列切片,数组切片或括号中,则为列表赋值,否则为是一个标量赋值。)

对标量上下文中的列表赋值的这种处理使得代码可能如下:

while ( my ($key, $value) = each %hash ) {

其中list-context每个都是一个迭代器,它返回(在列表上下文中)每个调用的一个键和值,并在完成时返回一个空列表,给while一个0并终止循环。

答案 1 :(得分:6)

首先,“列表”是一个含糊不清的术语。甚至问题也用它来指代两个不同的东西。我怀疑你可能在没有意识到这一点的情况下这样做,而且这是导致你混淆的一个重要原因。

我将使用“列表值”来表示操作符在列表上下文中返回的内容。相反,“列表运算符”是指运算符EXPR,EXPR,EXPR,...,也称为“逗号运算符” [1]

其次,您应该阅读Scalar vs List Assignment Operator


  

我想通过=从列表到列表的分配将一个列表中的项目的分配分配给另一个列表中的项目,这就是为什么在第二个示例中$ string2获取'stuff',以及为什么我们可以通过列表解压缩@_分配。

正确。

  

我已多次读过列表在标量上下文中实际不存在的内容。

这句话非常含糊不清。您似乎在谈论列表值(在内存中找到),但标量上下文仅存在于代码中(找到运算符的位置)。

  • 可以在标量上下文中评估列表/逗号运算符。
  • 无法在标量上下文中返回列表值。

标量上下文是可以评估运算符的上下文。

在标量上下文中计算的运算符无法返回列表。它必须返回一个标量。简而言之,您可以说在标量上下文中不能返回列表。

另一方面,可以在标量上下文中评估列表/逗号运算符。例如scalar(4,5,6)。每个操作符都可以在任何上下文中进行评估(尽管这样做并不一定有用)。

  

但是对goatse运算符的解释是它是标量上下文中的列表赋值。

包含一个,是的。

列表值和列表赋值运算符是两回事。一个人的价值。另一个是一段代码。

列表赋值运算符与其他运算符一样,可以在标量上下文中进行计算。标量上下文中的列表赋值运算符返回其RHS返回的标量数。

因此,如果您在标量上下文中评估() = qw(a b c),它将返回三个,因为qw()在堆栈上放置了三个标量。

  

但是,我不明白如何将()(空列表)的赋值分配给它。

就像赋值($x,$y) = qw(stuff junk things)忽略RHS返回的第三个元素一样,() = qw(stuff junk things)会忽略RHS返回的所有元素。

  

根据我目前的理解,由于列表是不可变的,列表的大小将保持为0

()=qw(a b c)说“列表的大小将保持为零”就像是对4+5说“标量的值将保持为4”。

对于初学者来说,问题是你在谈论哪个列表。 LHS返回一个,RHS返回一个,赋值运算符可能返回一个。

LHS返回的列表值将为0。

RHS返回的列表值为3。

在标量上下文中,列表赋值运算符返回RHS(3)返回的标量数。

在列表上下文中,列表赋值运算符将LHS返回的标量返回为左值(空列表)。

  

列表应该是不可变的。

如果您正在考虑列表可变性,那么您在某处出现了错误的转变。 [2]


注意:

  1. 文档调用EXPR,EXPR,EXPR,...二元运算符的两个实例,但它更容易理解为单个N元运算符,并且它实际上是作为单个N元运算符实现的。即使在标量背景下也是如此。

  2. 事实并非如此,但我们不要再往下走了。

答案 2 :(得分:6)

有必要记住,在Perl中,赋值是一个表达式,你应该考虑表达式的值(赋值运算符的值),而不是“列表的值”。

表达式qw(a b)的值在列表上下文中为('a', 'b'),在标量上下文中为'b',但表达式(() = qw(a b))的值为()列表上下文和标量上下文中的2(@a = qw(a b))的值遵循相同的模式。这是因为列表赋值运算符pp_aassign选择在标量上下文中返回计数:

else if (gimme == G_SCALAR) {
    dTARGET;
    SP = firstrelem;
    SETi(lastrelem - firstrelem + 1);
}

(pp_hot.c第1257行;行号可能会发生变化,但它接近PP(pp_aassign)的末尾。)

然后,除了赋值运算符的之外,还有赋值运算符的副作用。列表赋值的副作用是将值从右侧复制到左侧。如果右侧首先用完了值,则左侧的其余元素将获得undef;如果左侧首先用完了值,则不会复制右侧的其余元素。当给出()的LHS时,列表赋值不会复制任何内容。但是,赋值本身的价值仍然是RHS中元素的数量,如代码片段所示。