Perl "reverse comma operator"(《Programming Perl》第 4 版中的示例)

Perl "reverse comma operator" (Example from the book Programming Perl, 4th Edition)

我正在将“Programming Perl”和 运行 读入一个似乎没有意义的 st运行ge 示例。这本书描述了 Perl 中的逗号运算符如何 return 在标量上下文中使用时仅显示最后一个结果。

示例:

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

这本书在几页之后给出了 "reverse comma operator" 这个例子(第 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 到确切的页面。该示例靠近底部。

这是一个可爱而聪明的例子,但对它的思考太多会分散它的目的。这本书的那一部分展示了列表切片。除了对列表进行切片之外的任何事情,无论列表中有什么,都与示例的目的无关。


您只在一本非常大的书的第 82 页(我们确实无法再容纳更多的页面,因为我们已经达到了装订方法的极限),所以我们没有什么可以扔给您的。在其他列表切片示例中,有一个我不会在实际代码中使用的聪明示例。不过,这就是人为示例的诅咒。

但是假设有一个反向逗号运算符。它必须评估逗号的两边。许多答案都适合 "just return the first thing"。但这不是功能。即使保留其中一个,也必须访问每个表达式。

考虑一下这个更高级的版本,它包含一系列我立即取消引用的匿名子例程,每个子例程都会打印一些内容,然后 returns 结果:

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 内置 returns 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";

这表明对所有逗号两边的每个表达式都进行了求值。因为我们没有展示完整的例子,所以我加入了子程序包装器:

list: g h j k
list: g h

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

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

演示的内容很简单:

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

  • 但是,如果您将该序列放在括号中并在末尾添加 [0],它将将该序列变成一个列表并获取其第一个元素,例如

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

    出于某种原因,本书将其称为 "reverse comma operator"。这是用词不当;它只是一个列表,它的第一个元素已被采用。

这本书在参数中两次使用 pop 函数,进一步 混淆了事情。它们是从左到右计算的,因此第一个 pop 计算为 "three",第二个计算为 "two".

无论如何:永远不要在实际代码中使用逗号或 "reverse comma" 运算符。两者都可能会让未来的读者感到困惑。

让我们让例子更相似。

逗号运算符:

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

反向逗号运算符:

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

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

"reverse comma operator" 作为子例程的实现看起来像这样:

sub reverse_comma {
    return shift @_;
}

一个普通的逗号将评估其操作数,然后 return 右侧操作数的值

my $v = $a, $b

$v 设置为 $b

的值

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

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

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

$v 设置为 $a

的值

真的就是这样。这本书并没有试图建议应该有一个 reverse comma 子例程,它只是考虑按顺序评估两个表达式并 returning 第一个的问题。只有当两个表达式有副作用时,评估的顺序才相关,这就是为什么书中的示例使用 pop,它更改数组以及 returning 值

想象中的问题是这样的

Suppose I want a subroutine to remove the last two elements of an array, and then return the value of what used to be the last element

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

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

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

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

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

return scalar splice @foo, -2;

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

希望对你有所帮助