48

我最近一直在阅读有关 Perl 的文章,对 Perl 如何处理传递给子例程的参数感到有些困惑。

在 Python、Java 或 PHP 等语言中,函数定义采用以下形式(伪代码):

function myFunc(arg1, arg2) {
    // Do something with arg1 and arg2 here
}

然而在 Perl 中它只是:

sub mySub {
    # @_ holds all arguments passed
}

据我了解,这是唯一的方法。

  • 如果我想限制调用者只传递两个参数怎么办?

  • 这难道不是 Perl 只允许其他语言(即 Python、C 等)中的可变数字参数以外的任何东西吗?

  • 这在某个时候不会成为问题吗?

  • 其他语言中的所有默认参数编号检查怎么样?是否必须在 Perl 中明确地这样做?例如

      sub a_sub {
          if (@_ == 2) {
              # Continue function
          }
          else {
              return false
          }
      }
    
4

7 回答 7

82

您对 Perl 环境持谨慎态度,因为它与您之前遇到的语言完全不同。

相信强类型和函数原型的人在这里会不同意,但我相信这样的限制很少有用。C真的让你经常向函数传递错误数量的参数以至于有用吗?

在现代 Perl 中最常见的是将 的内容复制@_到词汇标量变量列表中,因此您经常会看到以

sub mysub {
  my ($p1, $p2) = @_;
  ... etc.
}

这样,所有传递的参数都将作为@_(等) 的元素使用,而$_[0]预期参数被命名并出现在and中(尽管我希望您理解应该适当地选择这些名称)。$_[1]$p1$p2

在子例程是方法的特殊情况下,第一个参数是特殊的。在其他语言中它是selfor this,但在 Perl 中它只是 in 的第一个参数@_,您可以随意称呼它。在那种情况下,你会看到

sub method {
  my $self = shift;
  my ($p1, $p2) = @_;
  ... etc.
}

以便上下文对象(或类的名称,如果它是类方法)被提取到$self(约定的名称)中,其余参数保留在@_其中以便直接访问,或者更常见的是复制到本地标量变量为$p1$p2

最常见的抱怨是也没有类型检查,所以我可以将我喜欢的任何标量作为子例程参数传递。只要use strictuse warnings在上下文中,即使这通常也很容易调试,因为子例程可以在一种形式的标量上执行的操作通常在另一种形式上是非法的。

虽然它最初更多是与面向对象的 Perl 的封装有关,但 Larry Wall 的这句话是非常相关的

Perl 并不热衷于强制隐私。它宁愿你呆在它的客厅外面,因为你没有被邀请,而不是因为它有猎枪

如果您可以在编译期间而不是在运行时让错误的程序失败,那么C 的设计和实现是在它是一个主要的效率提升的时代。现在情况发生了变化,尽管客户端 JavaScript 也出现了类似的情况,在从 Internet 获取它必须处理的数据之前知道代码错误实际上是有用的可悲的是,JavaScript 参数检查现在比应有的宽松。


更新

对于那些怀疑 Perl 用于教学目的的人,我建议正是因为Perl 的机制是如此简单和直接,所以它们非常适合这种目的。

  • 当您调用 Perl 子例程时,调用中的所有参数@_. 您可以直接使用它们来影响实际参数,或者复制它们以防止外部动作

  • 如果您将 Perl 子例程作为方法调用,那么调用对象或类将作为第一个参数提供。同样,子例程(方法)可以做它喜欢做的事情@_

于 2013-10-07T21:05:55.950 回答
23

Perl 不会为您管理参数处理。相反,它提供了一个最小的、灵活的抽象,并允许您编写适合您需要的代码。

通过引用传递

默认情况下,Perl 为@_. 这实现了基本的、按引用传递的语义。

my $num = 1;
foo($num);
print "$num\n";  # prints 2.

sub foo { $_[0]++ }

通过引用传递速度很快,但存在泄露参数数据更改的风险。

通过副本

如果你想通过复制语义,你需要自己制作副本。处理位置参数列表的两种主要方法在 Perl 社区中很常见:

sub shifty {
    my $foo = shift;
}

sub listy {
    my ($foo) = @_;
}

在我的工作地点,我们做了一个 listy 版本:

sub fancy_listy {

    my ($positional, $args, @bad) = @_;

    die "Extra args" if @bad;
}

命名参数

另一种常见的做法是使用命名参数

sub named_params {
    my %opt = @_;
}

有些人对上述内容感到满意。我更喜欢更详细的方法:

sub named_params {
    my %opt = @_;

    my $named = delete $opt{named} // "default value";
    my $param = delete $opt{param}
        or croak "Missing required 'param'";

    croak "Unknown params:", join ", ", keys %opt
        if %opt;

    # do stuff 
}

这会将命名参数解包到变量中,为基本验证和默认值留出空间,并强制不传入额外的未知参数。

关于 Perl 原型

Perl 的“原型”不是通常意义上的原型。它们只提供编译器提示,允许您跳过函数调用的括号。唯一合理的用途是模仿内置函数的行为。您可以轻松击败原型参数检查。一般来说,不要使用原型。使用它们时要小心,以免使用运算符重载——即谨慎使用它们,并且只是为了提高可读性。

于 2013-10-07T22:45:29.690 回答
8

出于某种原因,Perl 喜欢列表,而不喜欢静态类型。@_数组实际上提供了很大的灵活性,因为子例程参数是通过引用传递的,而不是通过值传递的。例如,这允许我们进行外置参数:

my $x = 40;
add_to($x, 2);
print "$x\n"; # 42

sub add_to { $_[0] += $_[1] }

......但这更像是一个历史性的性能黑客。通常,参数由列表赋值“声明”:

sub some_sub {
  my ($foo, $bar) = @_;
  #               ^-- this assignment performs a copy
  ...
}

这使得这个子按值调用的语义,这通常是更可取的。是的,未使用的参数只是被遗忘了,太少的参数不会引发任何自动错误——变量只包含undef. 您可以添加任意验证,例如通过检查@_.


有计划在未来最终使命名参数可用,看起来像

sub some_sub($foo, $bar) { ... }

如果您安装该模块,您今天就可以使用此语法。signatures但是还有更好的东西:我可以强烈推荐Function::Parameters,它允许像这样的语法

fun some_sub($foo, $bar = "default value") { ... }

method some_method($foo, $bar, :$named_parameter, :$named_with_default = 42) {
  # $self is autodeclared in methods
}

这也支持实验类型检查。

解析器扩展 FTW!

于 2013-10-07T22:05:36.267 回答
6

如果您真的想在 Perl 中实施更严格的参数检查,您可以查看类似Params::Validate的内容。

于 2013-10-08T13:58:31.463 回答
5

Perl 确实具有参数占位符的原型设计能力,你已经习惯了,但它通常是不必要的。

sub foo($){
    say shift;
}; 
foo();      # Error: Not enough arguments for main::foo
foo('bar'); # executes correctly

如果你这样做sub foo($$){...}了,它将需要 2 个非可选参数(例如foo('bar','baz')

于 2013-10-07T20:56:49.750 回答
3

您可以使用:

my ($arg1, $arg2) = @_;

要明确限制您可以使用的参数数量:

my $number =2;
die "Too many arguments" if @_ > $number;
于 2013-10-07T20:38:09.863 回答
2

如果您最近正在阅读 Perl,请阅读有关最近的 Perl。您也可以免费阅读Modern Perl书籍。

以下是该书中有关函数签名的一些示例:

sub greet_one($name = 'Bruce') {
    say "Hello, $name!";
}
sub greet_all($leader, @everyone) {
    say "Hello, $leader!";
    say "Hi also, $_." for @everyone;
}

sub make_nested_hash($name, %pairs) {
    return { $name => \%pairs };
}
于 2018-01-20T18:26:11.040 回答