【发布时间】:2013-05-15 07:45:58
【问题描述】:
为什么我们在 Perl 中使用函数原型? 有哪些不同的原型可用?如何使用它们?
例如:$$,$@,\@@ 它们是什么意思?
【问题讨论】:
-
预定义变量请参考perlvar。
-
他不是在谈论变量,而是在谈论原型。
-
一般来说,我们不会。
为什么我们在 Perl 中使用函数原型? 有哪些不同的原型可用?如何使用它们?
例如:$$,$@,\@@ 它们是什么意思?
【问题讨论】:
可以在官方文档中找到描述:http://perldoc.perl.org/perlsub.html#Prototypes
但更重要的是:阅读为什么你应该不使用函数原型”Why are Perl 5's function prototypes bad?
【讨论】:
要编写一些函数,原型是绝对必要的,因为它们会改变传递参数的方式、解析子调用以及评估参数的上下文。
下面是关于带有内置函数open 和bless 的原型的讨论,以及对用户编写代码(如fold_left 子例程)的影响。我得出的结论是,它们在一些场景中很有用,但它们通常不是处理签名的好机制。
CORE::open
一些内置函数有原型,例如open。你可以得到任何函数的原型,比如say prototype "CORE::open"。我们得到*;$@。这意味着:
* 采用裸字、glob、globref 或标量。例如。 STDOUT 或 my $fh。; 使以下参数成为可选参数。$ 评估标量上下文中的下一项。我们马上就会知道为什么这样做很好。@ 允许使用任意数量的参数。这允许像这样的调用
open FOO;(非常不好的风格,相当于open FOO, our $FOO)open my $fh, @array;,解析为open my $fh, scalar(@array)。没用的open my $fh, "<foo.txt";(风格不好,允许shell注入)open my $fh, "<", "foo.txt";(良好的三参数开放)open my $fh, "-|", @command;(现在 @command 在列表上下文中进行评估,即被展平)那么为什么第二个参数应该有标量上下文呢? (1) 要么你使用传统的双参数开放。那么访问第一个元素就不难了。 (2) 或者您想要 3-arg-open(而不是:multiarg)。然后在源代码中具有显式模式是必要的,这是一种很好的风格并减少了远处的动作。因此,这迫使您在过时的灵活 2-arg 或安全的多-arg 之间做出选择。
进一步的限制,例如 < 模式只能采用一个文件名,而 -| 采用至少一个字符串(命令)加上任意数量的参数,是在非语法级别上实现的。
CORE::bless
另一个有趣的例子是bless 函数。它的原型是$;$。 IE。需要一两个标量。
这允许bless $self;(祝福到当前包),或者更好的bless $self, $class。但是,my @array = ($self, $class); bless @array 不起作用,因为标量上下文被强加在第一个 arg 上。所以第一个参数不是引用,而是数字2。这减少了远距离的动作,并且失败而不是提供可能错误的解释:bless $array[0], $array[1] 或 bless \@array 都可能在这里表示。所以原型有助于和增强输入验证,但不能替代它。
fold_left
让我们定义一个函数fold_left,它接受一个列表和一个动作作为参数。它对列表的前两个值执行此操作,并用结果替换它们。这样循环直到只有一个元素,返回值就剩下了。
简单实现:
sub fold_left {
my $code = shift;
while ($#_) { # loop while more than one element
my ($x, $y) = splice @_, 0, 2;
unshift @_, $code->($x, $y);
}
return $_[0];
}
这可以这样称呼
my $sum = fold_left sub{ $_[0] + $_[1] }, 1 .. 10;
my $str = fold_left sub{ "$_[0] $_[1]" }, 1 .. 10;
my $undef = fold_left;
my $runtime_error = fold_left \"foo", 1..10;
但这并不令人满意:我们知道第一个参数是 sub,所以 sub 关键字是多余的。此外,我们可以在没有 sub 的情况下调用它,我们希望这是非法的。使用原型,我们可以解决这个问题:
sub fold_left (&@) { ... }
& 声明我们将采用 coderef。如果这是第一个参数,则允许省略 sub 关键字和子块后的逗号。现在我们可以做
my $sum = fold_left { $_[0] + $_[1] } 1 .. 10; # aka List::Util::sum(1..10);
my $str = fold_left { "$_[0] $_[1]" } 1 .. 10; # aka join " ", 1..10;
my $compile_error1 = fold_left; # ERROR: not enough arguments
my $compile_error2 = fold_left "foo", 1..10; # ERROR: type of arg 1 must be sub{} or block.
这让人想起map {...} @list
反斜杠原型允许在不强加上下文的情况下捕获对参数的类型化引用。当我们想要传递一个数组而不展平它时,这很好。例如
sub mypush (\@@) {
my ($arrayref, @push_these) = @_;
my $len = @$arrayref;
@$arrayref[$len .. $len + $#push_these] = @push_these;
}
my @array;
mypush @array, 1, 2, 3;
您可以想到 \ 保护 @ 就像在正则表达式中一样,因此需要在参数上使用文字 @ 字符。这就是原型是一个悲伤的故事的地方:要求文字字符是一个坏主意。我们甚至不能直接传递引用,我们必须先取消引用:
my $array = [];
mypush @$array, 1, 2, 3;
即使被调用的代码准确地看到并想要那个引用。从 v14 开始,可以改用 +。它接受一个数组、arrayref、hash 或 hashref(实际上,它就像 $ 用于标量参数,\[@%] 用于哈希和数组)。这个原型没有类型验证,它只会确保你收到一个引用,除非参数已经是标量。
sub mypush (+@) { ... }
my @array;
mypush @array, 1, 2, 3;
my $array_ref = [];
mypush $array_ref, 1, 2, 3; # works as well! yay
my %hash;
mypush %hash, 1, 2, 3; # syntactically legal, but will throw fatal on dereferencing.
mypush "foo", 1, 2, 3; # ditto
原型是让 Perl 随心所欲的好方法。最近我正在研究如何在 Perl 中实现函数式语言的模式匹配。 match 本身具有原型 $%(要匹配的一个标量事物,以及偶数个进一步的参数。这些是模式和代码对)。
它们也是一个很好的方式来射击自己的脚,并且可能非常丑陋。来自List::MoreUtils:
sub each_array (\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
return each_arrayref(@_);
}
这样可以调用each_array @a, @b, @c ...,但是直接调用each_arrayref \@a, \@b, \@c, ...并不费力,对参数个数没有限制,更灵活。
特别是像sub foo ($$$$$$;$$) 这样的参数表示代码异味,您应该移至命名参数、Method::Signatures 或 Params::Validate。
根据我的经验,好的原型是
@, % 啜饮任何(或偶数)数量的参数。请注意,@ 作为唯一原型相当于根本没有原型。& 领先的代码块以获得更好的语法。$ 如果你需要填充一个 slurpy @ 或 %,但不是自己。我非常不喜欢\@ 等,除了length 之外,还没有看到_ 的好用处(_ 可以是原型中最后一个必需的参数。如果没有给出明确的值,@使用987654391@。)
拥有良好的文档并要求您的用户在您的论点之前包含偶尔的反斜杠通常比远距离的意外动作或令人惊讶地强加标量上下文更可取。
原型可以像&foo(@args) 一样被覆盖,并且在方法调用中不受尊重,所以它们在这里已经没用了。
【讨论】: