您可以在官方文档中找到说明:http://perldoc.perl.org/perlsub.html#Prototypes
但更重要的是:了解为什么你应该不使用功能prototytpes" 为什么Perl 5的函数原型不好?
要编写一些函数,原型是绝对必要的,因为它们改变了参数传递的方式,解析了子调用,以及在什么上下文中评估参数.
下面是用内建的原型的讨论open
和bless
,以及对像用户编写的代码的效果fold_left
子程序.我得出的结论是,有一些场景它们很有用,但它们通常不是处理签名的好机制.
CORE::open
一些内置函数具有原型,例如open
.你可以得到任何函数的原型say prototype "CORE::open"
.我们得到了*;$@
.这意味着:
*
采取裸词,水珠,globref或标量.例如STDOUT
或my $fh
.;
使下面的参数可选.$
评估在标量上下文的下一个项目.我们会在一分钟内看到为什么这很好.@
允许任何数量的参数.这允许调用
open FOO;
(非常糟糕的风格,相当于open FOO, our $FOO
)open my $fh, @array;
,解析为open my $fh, scalar(@array)
.无用open my $fh, "<foo.txt";
(糟糕的风格,允许外壳注入)open my $fh, "<", "foo.txt";
(良好的三arg开放)open my $fh, "-|", @command;
(现在@command
在列表上下文中评估,即被展平)那么第二个参数为什么要有标量上下文呢?(1)要么使用传统的双arg-open.然后访问第一个元素并不困难.(2)或者你想要3-arg-open(而不是:multiarg).然后在源代码中具有显式模式是必要的,这是良好的风格并且减少了远处的动作.所以这迫使你决定过时的灵活2-arg或安全多arg.
进一步的限制,比如<
模式只能采用一个文件名,而-|
至少需要一个字符串(命令)加上任意数量的参数,都是在非语法层面上实现的.
CORE::bless
另一个有趣的例子是bless
函数.它的原型是$;$
.即需要一两个标量.
这允许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];
}
Run Code Online (Sandbox Code Playgroud)
这可以称为
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;
Run Code Online (Sandbox Code Playgroud)
但这并不令人满意:我们知道第一个参数是sub,因此sub
关键字是多余的.此外,我们可以在没有sub的情况下调用它,我们希望它是非法的.有了原型,我们可以解决这个问题:
sub fold_left (&@) { ... }
Run Code Online (Sandbox Code Playgroud)
在&
我们将采取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.
Run Code Online (Sandbox Code Playgroud)
这让人想起 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;
Run Code Online (Sandbox Code Playgroud)
您可以考虑在正则表达式中\
保护@
类似,因此需要@
在参数上使用文字字符.这就是原型是一个悲伤的故事:需要字面字符是一个坏主意.我们甚至不能直接传递引用,我们必须先取消引用它:
my $array = [];
mypush @$array, 1, 2, 3;
Run Code Online (Sandbox Code Playgroud)
即使被调用的代码看到并想要那个引用.从v14开始,+
可以使用它.它接受一个数组,arrayref,hash或hashref(实际上,它类似于$
标量参数,以及\[@%]
散列和数组).这个proto没有类型验证,它只是确保你收到一个引用,除非参数已经是标量.
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
Run Code Online (Sandbox Code Playgroud)
原型是将Perl弯曲到您的意志的好方法.最近我正在研究如何在Perl中实现函数式语言的模式匹配.它match
本身有原型$%
(一个要匹配的标量事物,以及偶数个进一步的参数.这些是模式和代码对).
它们也是用脚射击自己的好方法,也可能是彻头彻尾的难看.来自List::MoreUtils
:
sub each_array (\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
return each_arrayref(@_);
}
Run Code Online (Sandbox Code Playgroud)
这允许你把它称为each_array @a, @b, @c ...
,但直接做的不是很多each_arrayref \@a, \@b, \@c, ...
,这对参数的数量没有限制,而且更灵活.
特别是sub foo ($$$$$$;$$)
指示代码气味的参数,以及应该移动到命名参数,Method :: Signatures或Params :: Validate.
根据我的经验,好的原型是
@
,%
扼杀任何(或偶数)args.请注意,@
因为唯一原型相当于没有原型.&
领先的代码块以获得更好的语法.$
如果你需要填补一个邋,, @
或者%
不是他们自己.我不喜欢主动\@
等,还没有看到一个良好的使用_
从侧面length
(_
可以在一个原型的最后一个必要的参数.如果没有明确的给出值,$_
被使用.)
拥有一个好的文档并要求你的潜艇用户在你的论点之前包含偶然的反斜杠通常比远距离的意外动作或令人惊讶的标量上下文更可取.
原型可以被覆盖&foo(@args)
,并且不会在方法调用上受到尊重,因此它们在这里已经无用了.