4

我正在阅读“ 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];
}

这与正常的逗号运算符有何不同?结果是一样的,没有反转!

这是指向确切页面的链接。该示例位于底部附近。

4

4 回答 4

8

这是一个不好的例子,应该被遗忘。

它所展示的内容很简单:

  • 通常,如果您在标量上下文中有一系列用逗号分隔的表达式,则可以解释为逗号运算符的实例,该实例的计算结果为序列中的最后一项。

  • 但是,如果您将该序列放在括号中并[0]放在最后,它将将该序列转换为一个列表并获取其第一个元素,例如

    my $x = (1, 2, 3)[0];
    

    出于某种原因,本书将其称为“反逗号运算符”。这是用词不当;它只是一个列表,它的第一个元素被采用。

这本书通过在参数中两次使用该函数进一步混淆了问题。pop这些是从左到右评估的,因此第一个pop评估为"three",第二个评估为"two"

在任何情况下:永远不要在实际代码中使用逗号或“反逗号”运算符。两者都可能使未来的读者感到困惑。

于 2016-04-25T20:01:19.560 回答
6

这是一个可爱而聪明的例子,但想得太用力会分散其目的。这本书的那部分正在展示列表切片。除了对列表进行切片之外,无论列表中有什么,都与示例的目的无关。


您只在一本非常大的书的第 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";

副作用在那里很重要。

所以,回到骆驼,我们有一个例子,其中有副作用的东西是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";

这表明所有逗号两侧的每个表达式都被评估。因为我们没有展示一个完整的例子,所以我加入了子例程包装器:

list: g h j k
list: g h

但是,这些都不是该部分的重点,它正在索引列表文字。该示例的重点不是返回与其他示例或 Perl 功能不同的结果。假设逗号运算符的行为不同,它会返回相同的内容。该部分是关于列表切片的,并且正在展示列表切片,因此列表中的内容并不是重要的部分。

于 2016-04-25T19:59:48.500 回答
2

让我们使示例更加相似。

逗号运算符:

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

反逗号运算符:

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

这是一个“反逗号”,因为$scalar获取第一个表达式的结果,其中普通逗号运算符给出最后一个表达式。progn(如果你对 Lisp 有所了解,这就像和之间的区别prog1。)

“反逗号运算符”作为子例程的实现如下所示:

sub reverse_comma {
    return shift @_;
}
于 2016-04-25T20:38:17.707 回答
0

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

my $v = $a, $b

设置$v$b

为了演示列表切片,Camel 提出了一些代码,其行为类似于逗号运算符,但取而代之的是评估其操作数,然后返回左侧操作数的值

这样的事情可以用列表切片来完成,像这样

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

设置$v$a

这就是它的全部。这本书并没有试图建议应该有一个反向逗号子例程,它只是考虑按顺序评估两个表达式并返回第一个表达式的问题。仅当两个表达式具有副作用时,评估的顺序才相关,这就是书中示例使用 的原因pop,它更改了数组并返回了一个值

想象中的问题是这个

假设我想要一个子例程删除数组的最后两个元素,然后返回曾经是最后一个元素的值

通常这需要一个临时变量,像这样

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

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

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

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

return scalar splice @foo, -2;

但这不使用列表切片,这是本书该部分的主题。实际上,我怀疑这本书的作者是否会提出除了带有临时变量的简单解决方案之外的任何其他方法。这纯粹是一个列表切片可以做什么的例子

我希望这会有所帮助

于 2016-04-26T01:52:26.307 回答