为什么 Perl5的函数原型很糟糕?

另一个堆栈溢出问题 Leon Timmermans中断言:

我建议你不要使用原型。他们有他们的用途,但不是在大多数情况下,当然也不是在这一个。

为什么会是这样(或不是这样) ?我几乎总是为我的 Perl 函数提供原型,我以前从未见过其他人说使用它们有什么不好的地方。

19863 次浏览

问题在于 Perl 的函数原型并不像人们想象的那样。它们的用途是允许您编写像 Perl 内置函数那样进行解析的函数。

首先,方法调用完全忽略原型。如果您正在进行面向对象编程,那么您的方法具有什么样的原型并不重要。(所以他们不应该有任何原型。)

其次,原型并没有被严格执行。如果使用 &function(...)调用子例程,则忽略原型。所以它们并没有提供任何类型的安全性。

Third, they're spooky action-at-a-distance. (Especially the $ prototype, which causes the corresponding parameter to be evaluated in scalar context, instead of the default list context.)

特别是,它们使得很难从数组中传递参数。例如:

my @array = qw(a b c);


foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);


sub foo ($;$$) { print "@_\n" }


foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

印刷品:

a b c
a b
a b c
3
b
a b c

连同3个关于 main::foo() called too early to check prototype的警告(如果启用了警告)。问题是,在标量上下文中求值的数组(或数组片)返回数组的长度。

如果你需要编写一个像内置函数一样的函数,使用原型,否则就不要使用原型。

注意: Perl6将完全改进并且非常有用的原型。这个答案只适用于 Perl5。

如果使用正确,原型是不错的。困难在于 Perl 的原型并不像人们通常期望的那样工作。具有其他编程语言背景的人倾向于期望原型提供一种检查函数调用是否正确的机制: 也就是说,它们具有正确的参数数量和类型。Perl 的原型不太适合这项任务。坏的是 滥用。Perl 的原型有一个非常独特的用途:

原型允许您定义行为类似于内置函数的函数。

  • 括号是可选的。
  • 上下文强加在论点上。

例如,您可以像这样定义一个函数:

sub mypush(\@@) { ... }

称之为

mypush @array, 1, 2, 3;

而不需要写入 \来获取对数组的引用。

简而言之,原型允许您创建自己的语法代码。例如,Moose 框架使用它们来模拟更典型的 OO 语法。

这非常有用,但原型非常有限:

  • 它们必须在编译时可见。
  • 我们可以绕过他们。
  • 将上下文传播到参数可能导致意外行为。
  • 方法之外的任何东西都很难调用函数 严格规定的格式。

有关所有血淋淋的细节,请参见 perlsub 中的 原型机

我同意上面两张海报。一般来说,应该避免使用 $。原型只有在使用块参数(&)、 globs (*)或引用原型(\@\$\%\*)时才有用

有些人在看到 Perl 子例程原型时,认为它意味着一些它没有的东西:

sub some_sub ($$) { ... }

对于 Perl,这意味着解析器需要两个参数。这是 Perl 允许您创建内置子例程的方式,所有这些子例程都知道从后续代码中期望得到什么。您可以在 Perlsub中阅读有关原型的内容

在没有阅读文档的情况下,人们猜测原型是指运行时参数检查或者类似于他们在其他语言中看到的东西。正如人们对 Perl 的大多数猜测一样,它们最终被证明是错误的。

然而,从 Perl v5.20开始,Perl 有一个特性,在我写这篇文章的时候还处于试验阶段,它提供了更多类似于用户期望什么和什么的东西。Perl 的 子程序签名确实在运行时进行参数计数、变量分配和默认设置:

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);


animals( 'Buster', 'Nikki', 'Godzilla' );


sub animals ($cat, $dog, $lizard = 'Default reptile') {
say "The cat is $cat";
say "The dog is $dog";
say "The lizard is $lizard";
}

如果您正在考虑原型,那么这就是您可能需要的特性。