在比较 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
。如果您使用胖逗号语法调用带参数的子例程
# Perl 5
frobnicate( bar => 42 );
它将传递两个值,“foo”
和 42
,它们将作为值 42
与键 “foo”
关联放置到 %named
哈希中。但是,如果您指定
# Perl 5
frobnicate( "bar", 42 );
=>
是自动引用左侧的语法糖。否则,它的功能就像逗号一样(因此得名“胖逗号”)。
如果子例程作为带有命名参数的方法调用,则这种惯用法与标准惯用法结合使用
# 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 的(可变参数)展平语义,您可以使用所谓的“吞噬数组”来指示这一点,方法是在签名中以星号为数组添加前缀
# Perl 6
sub slurp-an-array(*@values) {
# do something with @values
}
slurp-an-array("foo", 42, "baz");
吞噬数组只能作为签名中的最后一个位置参数出现。
如果您希望使用 Perl 5 的方式在 Perl 6 中指定参数,您可以通过在签名中指定吞噬数组 *@_
来实现
# 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
}
普通(位置)参数和命名参数之间的区别在于冒号,它位于定义中的 类型符号 和变量名称之前
$foo # positional parameter, receives in $foo
:$bar # named parameter "bar", receives in $bar
除非另有说明,否则命名参数是可选的。如果未指定命名参数,则关联的变量将包含默认值,通常是类型对象 Any
。
如果您想捕获任何(其他)命名参数,您可以使用所谓的“吞噬哈希”。与吞噬数组一样,它用哈希之前的星号表示
# Perl 6
sub slurp-nameds(*%nameds) {
say "Received: " ~ join ", ", sort keys %nameds;
}
slurp-nameds(foo => 42, bar => 666); # Received: bar, foo
与吞噬数组一样,签名中只能有一个吞噬哈希,并且它必须在任何其他命名参数之后指定。
通常,您想从具有相同名称的变量将命名参数传递给子例程。在 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 提供了一种描述如何将子例程的参数捕获到该子例程的形参中的方法。位置参数由它们的名称和适当的类型符号指示(例如,$foo
)。命名参数以冒号为前缀(例如,:$bar
)。位置参数可以标记为 is rw
,以允许更改调用者范围内的变量。
位置参数可以在吞噬数组中展平,吞噬数组以星号为前缀(例如,*@values
)。意外的命名参数可以使用吞噬哈希收集,吞噬哈希也以星号为前缀(例如,*%nameds
)。
默认值可以在签名内指定,方法是在等号后添加表达式(例如,$foo = 42
),这会使该参数成为可选参数。
Perl 6 中的签名还有许多其他有趣的功能,除了此处总结的功能之外;如果您想了解有关它们的更多信息,请查看 Perl 6 签名对象文档。
3 条评论