本文说明了为什么在某些情况下,标量上下文中使用”列表“无法得到本身元素的个数,以及如何获得你所期望的结果。
1. “列表”在标量上下文中并非一定会得到元素的个数
或许你觉得这个标题有一些奇怪:按照Perl 上下文的定义,列表在标量上下文中会得到列表元素的个数。单纯通过这个逻辑来看,下面的语句似乎会让$scalar 被赋值3:
$scalar = qw/a b c/;
然而Perl 总会在一些不起眼的地方强行给你一个大惊喜,如果你打印$scalar 的值,你会发现你得到的是"c"而非"3"。
2. 为什么可以被称为真理的上下文规则“失效”了
似乎对于非Perl 程序员,说明这个问题要简单许多。为了理解为什么上下文规则在这里“失效”了,我们必须抛开“列表在标量上下文中会得到列表元素的个数”这样一个看似是真理的结论,而从语言本身再去看待这个问题。
仍然是这条语句
$scalar = qw/a b c/;
Perl 中所有的qw 运算符都会被展开成列表形式,所以这条语句和下一条完全等价:
$scalar = ('a', 'b', 'c',);
当然这两条语句都会把$scalar 赋值为"c"。
下面让我们忘掉上下文规则,通过Perl 的眼睛去看看在这里Perl 看到了什么:
1. 赋值号之后是一个左括号,而括号是一种优先级约束,括号中的表达式被优先计算。
2. 括号中是由一串被逗号隔开的表达式,所以理所当然的是一个逗号表达式。
3. 逗号表达式的值是最后一个表达式的值,最后一个表达式是'c',它的值也是'c'。
4. 所以整个逗号表达式的值是'c'
5. 将'c' 赋值给$scalar。
这就是Perl 的眼睛所看到的东西,也是为什么$scalar 会被赋值为'c' 的原因。
所以给你一条语句:
$scalar = scalar qw/a b c/;
如果你开始以Perl 的眼睛来看代码,你就会明白上面的语句和下一条语句等价:
$scalar = scalar 'c';
理所当然,你得到的仍然是"c" 而非"3"。
3. 我们所熟知的Perl 真理难道是谬论吗?
对于Perl 程序员有一个信条:上下文规则即是绝对的真理。因为这条规则就是Perl 的思考方式,正确性不容置疑。
当然,这条真理在上面的语句中仍然有效。
之所以会带来上下文规则失效了的错觉,是因为我们往往习惯以自己的眼睛而非Perl 的眼睛去看代码。刚才在解释$scalar 为什么会得到'c' 的时候,我们也间接地表达了一个事实,在语句
$scalar = qw/a b c/;
$scalar = ('a', 'b', 'c',);
$scalar = scalar qw/a b c/;
甚至是语句
$scalar = scalar ('a', 'b', 'c',);
中,都不存在任何列表。
上下文规则不可能出错,列表在标量上下文中返回的永远都是列表中元素的个数,然而在我们前面写的所有语句中,的确不曾出现过任何一个列表。
4. 如何在标量上下文中使用列表
如果想这样做,方法就是显式的告诉Perl 赋值号右边是一个列表而非逗号表达式:
$scalar = @{ ['a', 'b', 'c',] };
其中赋值号右边的方括号告诉Perl 这是一个匿名列表的引用,而@{$aref} 则是列表的解引用形式,所以赋值号右边我们得到了一个列表"('a', 'b', 'c')", 而非逗号表达式"('a', 'b', 'c')"。而根据上下文规则,列表在标量上下文中会得到列表元素的个数。理所当然$scalar 被赋值为3。
回过头来,让我们看另一条语句:
@array = qw/a b c/;
根据qw 展开规则,这条语句和下面一条语句等价:
@array = ('a', 'b', 'c',);
赋值号左边是一个列表,所以这条语句是一个列表上下文,Perl 会在列表上下文中将赋值号的右边理解为一个列表"('a', 'b', 'c',)" 而非逗号表达式"('a', 'b', 'c',)"。这个不难理解——在列表上下文中理所当然Perl 会期望一个列表,而在标量上下文中理所当然Perl 会期望一个标量。所以相同的表达式
('a', 'b', 'c',)
会在标量上下文中被当作一个逗号表达式,而在列表上下文中被认为是一个列表。Perl 只是在寻找它期望得到的东西。
5. 去理解Perl 而非让Perl 理解你
这件事是2 年前自己学Perl 的时候也非常不解的一件事,在查阅了不少资料后才理解为什么我的代码会这样运作。我觉得这也是一个Perl 程序员所必须经历的一个转变:
Perl 和其他语言有些不同——写Perl 的时候需要用Perl 的思维去思考,你需要理解Perl,而非让Perl 去理解你的代码。