3

Why do we use function protoypes in Perl? What are the different prototypes available? How to use them?

Example: $$,$@,\@@ what do they mean?

4

2 回答 2

8

你可以在官方文档中找到描述:http: //perldoc.perl.org/perlsub.html#Prototypes

但更重要的是:阅读为什么不应该使用函数原型“为什么 Perl 5 的函数原型不好?

于 2013-05-15T07:51:09.253 回答
5

要编写一些函数,原型是绝对必要的,因为它们改变了传递参数的方式,解析子调用,以及在什么上下文中评估参数。

open下面是关于带有内置函数和的原型的讨论bless,以及对用户编写的代码(如fold_left子例程)的影响。我得出的结论是,它们在一些场景中很有用,但它们通常不是处理签名的好机制。

例子:CORE::open

一些内置函数有原型,例如open. 您可以获得任何函数的原型,例如say prototype "CORE::open". 我们得到*;$@. 这表示:

  • 采用*裸字、glob、globref 或标量。例如STDOUTmy $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函数。它的原型是$;$. 即需要一个或两个标量。

这允许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(@_);
}

这样你就可以把它叫做 as each_array @a, @b, @c ...,但是直接做 并不费力each_arrayref \@a, \@b, \@c, ...,而且对参数的个数没有限制,而且更加灵活。

尤其是参数,如sub foo ($$$$$$;$$)指示代码异味,您应该移至命名参数、Method::Signatures 或 Params::Validate。

根据我的经验,好的原型是

  • @,%啜饮任何(或偶数)个参数。请注意,@作为唯一原型等同于根本没有原型。
  • &领先的代码块以获得更好的语法。
  • $如果您需要垫 slurpy@%,但不是自己。

我非常不喜欢\@等,并且还没有看到_除了length(_可以是原型中最后一个必需的参数。如果没有给出明确的值,$_则使用它。)

拥有良好的文档并要求您的潜艇用户在您的论点之前包含偶尔的反斜杠通常比远距离的意外动作或令人惊讶地强加标量上下文更可取。

原型可以像 一样被覆盖&foo(@args),并且在方法调用上不被尊重,所以它们在这里已经没用了。

于 2013-05-15T10:05:31.660 回答