在本系列比较 Perl 5 和 Perl 6 的第一篇文章中,我们研究了将代码迁移到 Perl 6 时可能遇到的一些问题。在第二篇文章中,我们考察了 Perl 6 中的垃圾回收是如何工作的,在第三篇文章中,我们研究了容器如何在 Perl 6 中取代引用。在本文(第四篇)中,我们将重点介绍 Perl 6 中的(子例程)签名,以及它们与 Perl 5 中的签名有何不同。
Perl 5 中的实验性签名
如果您正在从 Perl 5 代码迁移到 Perl 6,您可能没有使用 Perl 5.20 中提供的实验性签名功能,或者任何旧的 CPAN 模块,如 signatures、Function::Parameters,或 CPAN 上任何其他名称中带有 “signature” 的 Perl 5 模块。
此外,根据我的经验,原型在世界各地的 Perl 程序中(例如 DarkPAN)使用得并不多。
由于这些原因,我将仅将 Perl 6 功能与 “经典” Perl 5 参数传递的最常见用法进行比较。
Perl 5 中的参数传递
您传递给 Perl 5 子例程的所有参数都会被展平并放入内部自动定义的 @_
数组变量中。这基本上就是 Perl 5 在将参数传递给子例程时所做的一切。仅此而已,仅此而已。然而,Perl 5 中有几种惯用法可以从那里开始。根据我的经验,最常见的(我会说是 “标准”)惯用法是
# Perl 5
sub do_something {
my ($foo, $bar) = @_;
# actually do something with $foo and $bar
}
这种惯用法执行列表赋值(复制)给两个(新的)词法变量。Perl 6 也支持这种访问子例程参数的方式,但它仅旨在使迁移更容易。
如果您期望固定数量的参数后跟可变数量的参数,则通常使用以下惯用法
# Perl 5
sub do_something {
my $foo = shift;
my $bar = shift;
for (@_) {
# do something for each element in @_
}
}
这种惯用法依赖于 shift 的神奇行为,它在这种上下文中从 @_
中移位。如果子例程旨在作为方法调用,则通常会看到类似这样的内容
# Perl 5
sub do_something {
my $self = shift;
# do something with $self
}
因为传递的第一个参数是 Perl 5 中的 调用者。
顺便说一下,这种惯用法也可以用第一种惯用法编写
# Perl 5
sub do_something {
my ($foo, $bar, @rest) = @_;
for (@rest) {
# do something for each element in @rest
}
}
但这效率会较低,因为它会涉及复制一个可能很长的值列表。
第三种惯用法围绕直接访问 @_
数组。
# Perl 5
sub sum_two {
return $_[0] + $_[1]; # return the sum of the two parameters
}
这种惯用法通常用于小型、单行子例程,因为它是处理参数的最有效方法之一,因为不进行复制。
如果您想更改作为参数传递的任何变量,也会使用这种惯用法。由于 @_
中的元素是指定变量的别名(在 Perl 6 中,您会说:“绑定到变量”),因此可以更改内容
# Perl 5
sub make42 {
$_[0] = 42;
}
my $a = 666;
make42($a);
say $a; # 42
Perl 5 中的命名参数
命名参数(就其本身而言)在 Perl 5 中不存在。但是,有一种常用的惯用法可以有效地模拟命名参数
# Perl 5
sub do_something {
my %named = @_;
if (exists %named{bar}) {
# do stuff if named variable "bar" exists
}
}
这通过从 @_
数组交替获取键和值来初始化哈希 %named
。如果您使用 fat-comma 语法调用带有参数的子例程
# Perl 5
frobnicate( bar => 42 );
它将传递两个值,"foo"
和 42
,它们将作为值 42
与键 "foo"
关联放入 %named
哈希中。但是,如果您指定
# Perl 5
frobnicate( "bar", 42 );
=>
是自动引用左侧的语法糖。否则,它的功能就像逗号一样(因此得名 “fat comma”)。
如果子例程作为带有命名参数的方法调用,则此惯用法与标准惯用法结合使用
# Perl 5
sub do_something {
my ($self, %named) = @_;
# do something with $self and %named
}
或者
# Perl 5
sub do_something {
my $self = shift;
my %named = @_;
# do something with $self and %named
}
Perl 6 中的参数传递
在最简单的形式中,Perl 6 中的子例程签名非常类似于 Perl 5 的 “标准” 惯用法。但它们不是代码的一部分,而是子例程定义的一部分,您不需要执行赋值
# Perl 6
sub do-something($foo, $bar) {
# actually do something with $foo and $bar
}
与
# Perl 5
sub do_something {
my ($foo, $bar) = @_;
# actually do something with $foo and $bar
}
在 Perl 6 中,($foo, $bar)
部分称为子例程的签名。
由于 Perl 6 具有实际的 method
关键字,因此无需考虑调用者,因为它可以通过 self
术语自动获得
# Perl 6
class Foo {
method do-something-else($foo, $bar) {
# do something else with self, $foo and $bar
}
}
此类参数在 Perl 6 中称为位置参数。除非另有说明,否则在调用子例程时必须指定位置参数。
如果您需要直接在 Perl 5 中使用 $_[0]
的别名行为,可以通过指定 is rw
特征将参数标记为可写
# Perl 6
sub make42($foo is rw) {
$foo = 42;
}
my $a = 666;
make42($a);
say $a; # 42
当您将数组作为参数传递给子例程时,它不会在 Perl 6 中展平。您只需要在签名中接受数组作为数组
# Perl 6
sub handle-array(@a) {
# do something with @a
}
my @foo = "a" .. "z";
handle-array(@foo);
您可以传递任意数量的数组
# Perl 6
sub handle-two-arrays(@a, @b) {
# do something with @a and @b
}
my @bar = 1..26;
handle-two-arrays(@foo, @bar);
如果您想要 Perl 5 的 (可变参数) 展平语义,您可以使用所谓的 “slurpy array” 通过在签名中用星号作为数组前缀来指示这一点
# Perl 6
sub slurp-an-array(*@values) {
# do something with @values
}
slurp-an-array("foo", 42, "baz");
slurpy array 只能作为签名中的最后一个位置参数出现。
如果您喜欢使用 Perl 5 的方式在 Perl 6 中指定参数,您可以通过在签名中指定 slurpy array *@_
来实现
# Perl 6
sub do-like-5(*@_) {
my ($foo, $bar) = @_;
}
Perl 6 中的命名参数
在调用端,Perl 6 中的命名参数可以非常类似于 Perl 5 中的表达方式
# Perl 5 and Perl 6
frobnicate( bar => 42 );
但是,在子例程的定义端,情况却大相径庭
# Perl 6
sub frobnicate(:$bar) {
# do something with $bar
}
普通(位置)参数和命名参数之间的区别在于冒号,它位于 sigil 和定义中的变量名称之前
$foo # positional parameter, receives in $foo
:$bar # named parameter "bar", receives in $bar
除非另有说明,否则命名参数是可选的。如果未指定命名参数,则关联的变量将包含默认值,通常是类型对象 Any
。
如果您想捕获任何(其他)命名参数,您可以使用所谓的 “slurpy hash”。与 slurpy array 一样,它通过哈希之前的星号来指示
# Perl 6
sub slurp-nameds(*%nameds) {
say "Received: " ~ join ", ", sort keys %nameds;
}
slurp-nameds(foo => 42, bar => 666); # Received: bar, foo
与 slurpy array 一样,签名中只能有一个 slurpy hash,并且必须在任何其他命名参数之后指定。
通常,您希望从具有相同名称的变量将命名参数传递给子例程。在 Perl 5 中,这看起来像:do_something(bar => $bar)
。在 Perl 6 中,您可以以相同的方式指定它:do-something(bar => $bar)
。但是您也可以使用快捷方式:do-something(:$bar)
。这意味着更少的输入 – 以及更少的拼写错误机会。
Perl 6 中的默认值
Perl 5 具有以下惯用法,用于使带有默认值的参数成为可选参数
# Perl 5
sub dosomething_with_defaults {
my $foo = @_ ? shift : 42;
my $bar = @_ ? shift : 666;
# actually do something with $foo and $bar
}
在 Perl 6 中,您可以通过指定等号和表达式,将默认值指定为签名的一部分
# Perl 6
sub dosomething-with-defaults($foo = 42, :$bar = 666) {
# actually do something with $foo and $bar
}
如果为位置参数指定了默认值,则位置参数将变为可选参数。命名参数保持可选,而与任何默认值无关。
总结
Perl 6 提供了一种描述如何将子例程的参数捕获到该子例程的参数中的方法。位置参数由它们的名称和适当的 sigil(例如,$foo
)指示。命名参数以冒号作为前缀(例如,:$bar
)。位置参数可以标记为 is rw
,以允许更改调用者范围内的变量。
位置参数可以在 slurpy array 中展平,slurpy array 以星号为前缀(例如,*@values
)。可以使用 slurpy hash 收集意外的命名参数,slurpy hash 也以星号为前缀(例如,*%nameds
)。
默认值可以在签名内指定,方法是在等号后添加表达式(例如,$foo = 42
),这会使该参数成为可选参数。
Perl 6 中的签名还有许多其他有趣的功能,除了此处总结的功能外;如果您想了解更多关于它们的信息,请查看 Perl 6 签名对象文档。
3 条评论