为什么我们在Perl中使用函数原型? 有哪些不同的原型?如何使用它们?
示例:$$,$@,\@@
这是什么意思?
答案 0 :(得分:8)
您可以在官方文档中找到说明:http://perldoc.perl.org/perlsub.html#Prototypes
但更重要的是:了解为什么你应该不使用函数prototytpes“Why are Perl 5's function prototypes bad?
答案 1 :(得分:5)
要编写一些函数,原型是绝对必要的,因为它们改变了参数传递的方式,解析了子调用,以及在什么上下文中评估参数。
以下是有关内置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";
(良好的三arg-open)open my $fh, "-|", @command;
(现在@command
在列表上下文中进行评估,即展平)那么为什么第二个参数有标量上下文? (1)要么使用传统的双arg-open。然后,访问第一个元素并不困难。 (2)或者你想要3-arg-open(而不是:multiarg)。然后在源代码中具有显式模式是必要的,这是良好的风格并且减少了远处的动作。因此,这迫使您决定过时的灵活2-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];
}
这可以称为
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
反斜杠原型允许捕获对参数的类型引用,而不强加上下文。当我们想要传递一个数组而不展平它时,这是很好的。 E.g。
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(实际上,它在标量参数上类似$
,在哈希和数组上都是\[@%]
)。这个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
原型是将Perl屈服于您的意愿的好方法。最近我正在研究如何在Perl中实现函数式语言的模式匹配。 match
本身具有原型$%
(一个要匹配的标量事物,以及偶数个其他参数。这些是模式和代码对)。
它们也是用脚射击自己的好方法,而且可能是彻头彻尾的丑陋。来自List::MoreUtils
:
sub each_array (\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
return each_arrayref(@_);
}
这允许您将其称为each_array @a, @b, @c ...
,但直接执行each_arrayref \@a, \@b, \@c, ...
并不费力,这对参数数量没有限制,并且更加灵活。< / p>
特别是像sub foo ($$$$$$;$$)
这样的参数表示代码气味,您应该移动到命名参数,方法::签名或Params :: Validate。
根据我的经验,好的原型是
@
,%
扼杀任何(或偶数)args。请注意,@
作为唯一原型相当于没有原型。&
领先的代码块,用于更好的语法。$
如果您需要填写一个标签@
或%
,而不是自己填写。我主动不喜欢\@
等,除了_
之外,还没有看到length
的良好用法(_
可能是原型中最后一个必需的参数。如果没有给出明确的值,则使用$_
。)
拥有良好的文档并要求你的潜艇用户在你的论点之前包含偶然的反斜杠通常比远距离的意外行动或者令人惊讶的标量上下文更好。
原型可以像&foo(@args)
一样被覆盖,并且不会在方法调用中受到尊重,所以它们在这里已经无用了。