为什么Perl 5的功能原型很糟糕?

Aln*_*tak 116 perl prototype function

另一个Stack Overflow问题中, Leon Timmermans断言:

我建议你不要使用原型.它们有它们的用途,但对大多数情况并非如此,绝对不是在这个例子中.

为什么这可能是真的(或其他)?我几乎总是为我的Perl函数提供原型,而且我从来没有见过其他任何人说使用它们的坏事.

Mic*_*man 121

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

原型允许您定义与内置函数类似的函数.

  • 括号是可选的.
  • 上下文强加于参数.

例如,您可以定义这样的函数:

sub mypush(\@@) { ... }
Run Code Online (Sandbox Code Playgroud)

并称之为

mypush @array, 1, 2, 3;
Run Code Online (Sandbox Code Playgroud)

无需编写\以获取对数组的引用.

简而言之,原型可以让你创建自己的语法糖.例如,Moose框架使用它们来模拟更典型的OO语法.

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

  • 它们必须在编译时可见.
  • 他们可以被绕过.
  • 将上下文传播到参数可能会导致意外行为.
  • 它们使得使用除严格规定的表格之外的任何东西来调用函数变得困难.

有关所有血腥细节,请参阅perlsub中的Prototypes.

  • [远远超过你想知道的关于Perl原型的一切](http://www.perlmonks.org/?node_id=861966). (14认同)
  • 我已经接受了这个答案,因为我觉得它最能回答这个问题 - 原型并不是本质上不好的,它只是你如何使用它们. (2认同)
  • 另一方面,Moose原型是/ awesome/http://p3rl.org/MooseX::Declare http://p3rl.org/MooseX::Method :: Signign (2认同)

cjm*_*cjm 69

问题是Perl的功能原型并没有像人们认为的那样做.它们的目的是允许您编写将像Perl的内置函数一样进行解析的函数.

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

其次,原型并未严格执行.如果使用子程序调用&function(...),则忽略原型.所以他们并没有真正提供任何类型的安全.

第三,他们是远距离的幽灵行动.(特别是$原型,它导致在标量上下文中计算相应的参数,而不是默认的列表上下文.)

特别是,它们使得从数组传递参数变得困难.例如:

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]);
Run Code Online (Sandbox Code Playgroud)

打印:

a b c
a b
a b c
3
b
a b c
Run Code Online (Sandbox Code Playgroud)

以及3个警告main::foo() called too early to check prototype(如果启用了警告).问题是在标量上下文中计算的数组(或数组切片)返回数组的长度.

如果您需要编写一个类似于内置函数的函数,请使用原型.否则,不要使用原型.

注意:Perl 6将完全改造并且非常有用的原型.这个答案仅适用于Perl 5.

  • 更正:数组切片返回*list*,因此标量上下文中的数组切片返回列表的最后一个元素.你对`foo()`的倒数第二次调用打印2,因为这是你的两个元素切片中的最后一个元素.改为"我的@array = qw(foo bar baz)",你会看到差异.(顺便说一句,这就是为什么我不会在一次性的示范代码中将数组/列表初始化为基于0或1的数字序列.上下文中的索引,计数和元素之间的混淆不止一次地困扰我.傻但真实.) (10认同)
  • 有更好的方法来验证参数,例如Params :: Validate模块:http://search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/Validate.pm (5认同)
  • 没有; 普遍的共识是Perl函数原型基本上没有带来任何好处.你也可以不打扰他们,至少在Perl 5中.Perl 6可能是一个不同的(更好的)故事. (2认同)
  • @pilcrow:我编辑了使用`abc`的答案,让你的观点更加清晰. (2认同)

Leo*_*ans 30

我同意上述两张海报.通常,$应避免使用.原型中使用块参数(当只有有用&),水珠(*),或参考原型(\@,\$,\%,\*)

  • 一般来说,也许是这样,但我想提一下两个例外:首先,`($)` 原型创建一个命名的一元运算符,它可能很有用(当然 Perl 发现它们很有用;有时我也有)。其次,当重写内置函数时(无论是通过导入还是使用 CORE::GLOBAL::),您通常应该坚持内置函数具有的任何原型,即使其中包含“$”,否则您可能会让程序员感到惊讶(甚至是你自己)使用列表上下文,否则内置函数将提供标量上下文。 (2认同)

bri*_*foy 5

有些人在查看 Perl 子程序原型时,认为这意味着它不具有的含义:

sub some_sub ($$) { ... }
Run Code Online (Sandbox Code Playgroud)

对于 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";
    }
Run Code Online (Sandbox Code Playgroud)

如果您正在考虑原型,这就是您可能想要的功能。