Perl“反逗号运算符”(示例摘自《Perl编程》第4版)

jchrr9hc  于 2022-11-15  发布在  Perl
关注(0)|答案(4)|浏览(266)

我正在阅读“Programming Perl“,遇到了一个奇怪的例子,似乎没有意义。这本书描述了Perl中的逗号操作符在标量上下文中使用时如何只返回最后一个结果。

范例:

# After this statement, $stuff = "three"
$stuff = ("one", "two", "three");

书中然后在几页后给出了这个“反逗号运算符”的例子(第82页)

# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];

然而,对我来说,这似乎并不完全相反...?

范例:

# After this statement, $stuff = "three"
$stuff = reverse_comma("one", "two", "three");

# Implementation of the "reverse comma operator"
sub reverse_comma {
    return (pop(@_), pop(@_))[0];
}

这怎么会与正常的逗号运算符相反呢?结果是一样的,而不是相反的!

  • 这里是一个link到确切的页面。例子在底部附近。*
svmlkihl

svmlkihl1#

这是一个糟糕的例子,应该被忘记。
它所展示的很简单:

  • 通常,如果在标量上下文中有一个由逗号分隔的表达式序列,则可以将其解释为逗号运算符的一个示例,该示例计算为序列中的最后一个元素。
  • 然而,如果你把这个序列放在括号里,并把[0]放在最后,它就会把这个序列变成一个列表,并取它的第一个元素,例如:
my $x = (1, 2, 3)[0];

出于某种原因,这本书称之为“反逗号运算符”。这是用词不当;它只是一个列表的第一个元素。
这本书在参数中使用了两次pop函数,这使问题变得更加混乱。这些函数是从左到右求值的,所以第一个pop的求值结果是"three",第二个是"two"
在任何情况下:不要在真实的代码中使用逗号或“反逗号”操作符。这两种操作符都可能会让未来的读者感到困惑。

vof42yt1

vof42yt12#

这是一个可爱而聪明的例子,但是太过认真地思考它会分散你的注意力,因为这一部分是在炫耀列表切片,而除了切片之外的任何东西,无论列表中有什么,都与例子的目的无关。
你只读到了一本很大的书的第82页(因为绑定方法已经到了极限,我们真的无法再写更多的页了),所以我们也没有什么可以给你的了。在其他列表切片示例中,有一个很聪明的例子,我不会在真实的代码中使用。这就是人为示例的诅咒。
但假设有一个反向逗号运算符。它必须计算逗号的两边。许多答案都是正确的“只返回第一件事”。但这不是它的功能。即使你保留了一个表达式,你也必须访问每个表达式。
考虑一下这个高级得多的版本,它包含一系列匿名子例程,我立即取消引用这些子例程,每个子例程打印一些内容,然后返回一个结果:

use v5.10;

my $scalar = (
    sub { say "First"; 35 } -> (),
    sub { say "wantarray is ", 0+wantarray } -> (),
    sub { say "Second"; 27 } -> (),
    sub { say "Third"; 137 } -> ()
    );

因为赋值运算符比逗号运算符绑定得更紧,所以括号只是为了优先考虑。这里没有列表,尽管看起来有。
输出显示Perl对每个都进行了计算,尽管它保留了最后一个:

First
wantarray is 0
Second
Third
Scalar is [137]

名称不正确的wantarray内置函数返回false,说明子例程认为它位于标量上下文中。
现在,假设你想把它翻转过来,这样它仍然对每个表达式求值,但保留第一个表达式。你可以使用一个文本列表访问:

my $scalar = (
    sub { say "First"; 35 } -> (),
    sub { say "wantarray is ", 0+wantarray } -> (),
    sub { say "Second"; 27 } -> (),
    sub { say "Third"; 137 } -> ()
    )[0];

添加订阅后,右边现在是一个列表,我取出第一项。注意,第二个子例程认为它现在处于列表上下文中。我得到第一个子例程的结果:

First
wantarray is 1
Second
Third
Scalar is [35]

但是,让我们把它放在一个子例程中。我仍然需要调用每个子例程,即使我不会使用其他子例程的结果:

my $scalar = reverse_comma(
    sub { say "First"; 35 },
    sub { say "wantarray is ", 0+wantarray },
    sub { say "Second"; 27 },
    sub { say "Third"; 137 }
    );

say "Scalar is [$scalar]";

sub reverse_comma { ( map { $_->() } @_ )[0] }

或者,我会使用其他表达式的结果吗?如果我做了一些稍微不同的事情会怎样?我会在计算出的表达式中添加设置$last的副作用:

use v5.10;

my $last;

my $scalar = reverse_comma(
    sub { say "First"; 35 },
    sub { say "wantarray is ", 0+wantarray },
    sub { say "Second"; 27 },
    sub { say "Third"; 137 }
    );

say "Scalar is [$scalar]";
say "Last is [$last]";

sub reverse_comma { ( map { $last = $_->() } @_ )[0] }

现在我看到了标量逗号的有趣之处,它可以计算所有的表达式,其中一些表达式可能会产生副作用:

First
wantarray is 0
Second
Third
Scalar is [35]
Last is [137]

tchrist对shell脚本得心应手,这并不是一个大秘密。Perl到csh的转换器基本上就是他的收件箱(或comp.lang.perl)。你会在他的例子中看到一些类似shell的习惯用法。比如用一条语句交换两个数值的技巧:

use v5.10;

my $x = 17;
my $y = 137;

say "x => $x, y => $y";

$x = (
    $x = $x + $y,
    $y = $x - $y,
    $x - $y,
    );

say "x => $x, y => $y";

副作用很重要。
回到Camel,我们有一个例子,其中具有副作用的是pop数组运算符:

use v5.10;

my @foo = qw( g h j k );

say "list: @foo";

my $scalar = sub { return ( pop(@foo), pop(@foo) )[0] }->();

say "list: @foo";

这表明所有逗号两边的每个表达式都被求值了。由于我们没有给出一个完整的例子,所以我在子例程 Package 器中抛出了这个函数:

list: g h j k
list: g h

但是,这些都不是该节的重点,该节是索引到列表文字中。该示例的重点不是返回与其他示例或Perl特性不同的结果。它是在假设逗号操作符的作用不同的情况下返回相同的结果。该节是关于列表切片的,并展示了列表切片,因此列表中的内容并不是重要的部分。

r6vfmomb

r6vfmomb3#

让我们让这些例子更加相似。
逗号运算符:

$scalar = ("one", "two", "three");
# $scalar now contains "three"

反向逗号运算符:

$scalar = ("one", "two", "three")[0];
# $scalar now contains "one"

这是一个“反逗号”,因为$scalar得到第一个表达式的结果,而普通的逗号运算符给出最后一个表达式(如果你对Lisp有所了解,这就像prognprog1之间的区别)。
将“反逗号运算符”实现为子例程,如下所示:

sub reverse_comma {
    return shift @_;
}
yrwegjxp

yrwegjxp4#

普通逗号将计算其操作数,然后返回右侧操作数的值

my $v = $a, $b

$v设置为$b的值
为了演示列表切片,Camel提出了一些代码,这些代码的行为类似于逗号运算符,但它计算其操作数,然后返回 * left * 操作数的值
类似的操作可以通过列表切片来完成,如下所示

my $v = ($a, $b)[0]

它将$v设置为$a的值
这就是它的全部内容。这本书并没有试图建议应该有一个 reverse comma 子例程,它只是考虑了按顺序计算两个表达式并返回第一个表达式的问题。只有当两个表达式有副作用时,计算的顺序才是相关的,这就是为什么书中的示例使用pop,它改变数组并返回一个值
想象中的问题是
假设我想要一个子例程删除数组的最后两个元素,然后返回最后一个元素的值
通常这需要一个临时变量,如下所示

my $last = pop @foo;
pop @foo;
return $last;

但是,* 作为列表切片的示例 *,代码建议这也可以工作

# A "reverse comma operator".
return (pop(@foo), pop(@foo))[0];

请理解,这不是一个建议。有几种方法可以做到这一点。另一个单一语句的方法是

return scalar splice @foo, -2;

但它没有使用列表切片,而这正是本书这一节的主题。实际上,我怀疑本书的作者除了使用临时变量的简单解决方案之外,是否还会提出其他任何建议。
我希望这对你有帮助

相关问题