Perl子例程参数

Ari*_*des 40 perl

我最近一直在阅读有关Perl的内容,并且对于Perl如何处理传递给子例程的参数感到有些困惑.

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

function myFunc(arg1, arg2) {
    // Do something with arg1 and arg2 here
}
Run Code Online (Sandbox Code Playgroud)

然而在Perl中,它只是:

sub mySub {
    # @_ holds all arguments passed
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这不仅仅是Perl在其他语言(即Python,C等)中不允许任何变量数量参数吗?

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

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

    sub a_sub {
        if (@_ == 2) {
            # Continue function
        }
        else {
            return false
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

Bor*_*din 74

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

相信强类型和函数原型的人在这里会有不同意见,但我相信这样的限制很少有用.C是否真的抓住了你将错误数量的参数传递给一个经常足够有用的函数?

现代Perl中最常见的是将内容复制@_到词法标量变量列表中,因此您经常会看到以子句开头的子例程

sub mysub {
  my ($p1, $p2) = @_;
  ... etc.
}
Run Code Online (Sandbox Code Playgroud)

这样,传递的所有参数都将作为@_(等)的元素提供$_[0],$_[1]预期的参数将被命名并出现在($p1并且$p2我希望您理解这些名称应该被恰当地选择).

在子例程是方法的特定情况下,第一个参数是特殊的.在其他语言中它是self或者this,但在Perl中它只是第一个参数@_,你可以称它为你喜欢的.在那些情况下你会看到

sub method {
  my $self = shift;
  my ($p1, $p2) = @_;
  ... etc.
}
Run Code Online (Sandbox Code Playgroud)

这样上下文对象(或类的名称,如果它是一个类方法)被提取到$self(约定的名称)中,其余的参数仍然@_可以直接访问,或者更常见的是,复制到本地标量变量$p1,$p2等等.

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

虽然最初更多的是关于面向对象Perl的封装,但Larry Wall的引用非常相关

Perl对强制隐私没有迷恋.因为你没有被邀请,所以你宁愿呆在客厅外面,也不是因为它有霰弹枪

如果你可以在编译过程中而不是在运行时使一个错误的程序失败,那么C的设计和实现是在一个主要的效率提升的日子里.现在已经改变,虽然类似的情况已经与客户端JavaScript出现在那里,它实际上是知道这些代码是从它必须处理与互联网获取数据之前错误有用.遗憾的是,JavaScript参数检查现在比它应该更宽松.


更新

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

  • 当你调用一个Perl函数的所有呼叫参数的别名@_.您可以直接使用它们来影响实际参数,或者复制它们以防止外部操作

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

  • @Borodin:你可能会批评"强类型和函数原型"错误.能够使用类型化参数和声明参数的关键是编译器能够对它们进行编译时检查和优化(与其他语言一样).Larry明确地将这个门打开,直到实现了这种可能性,但是到目前为止p5p严格违反了这种编译器优化.正确实现的参数处理导致外部编译器的运行时间改进速度提高了10倍,但是当p5p正在评估此路径时,它选择采用缓慢的方式并始终复制@_. (4认同)

dao*_*oad 22

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

通过参考传递

默认情况下,Perl为每个参数添加一个别名@_.这实现了基本的,通过引用传递语义.

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

sub foo { $_[0]++ }
Run Code Online (Sandbox Code Playgroud)

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

通过复制

如果您希望通过复制语义进行传递,则需要自己制作副本.处理位置参数列表的两种主要方法在Perl社区中很常见:

sub shifty {
    my $foo = shift;
}

sub listy {
    my ($foo) = @_;
}
Run Code Online (Sandbox Code Playgroud)

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

sub fancy_listy {

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

    die "Extra args" if @bad;
}
Run Code Online (Sandbox Code Playgroud)

命名参数

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

sub named_params {
    my %opt = @_;
}
Run Code Online (Sandbox Code Playgroud)

有些人对上述情况感到满意.我更喜欢更详细的方法:

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

这会将名称params解包为变量,允许空间进行基本验证和默认值,并强制不传递额外的未知参数.

关于Perl原型

Perl的"原型" 不是正常意义上的原型.它们仅提供编译器提示,允许您跳过函数调用的括号.唯一合理的用途是模仿内置函数的行为.您可以轻松地击败原型参数检查.一般情况下,请勿使用原型.小心使用它们会使用运算符重载 - 即谨慎使用,只是为了提高可读性.


amo*_*mon 8

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

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

sub add_to { $_[0] += $_[1] }
Run Code Online (Sandbox Code Playgroud)

......但这更像是历史性的表演黑客.通常,参数由列表赋值"声明":

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

这使得这个sub-call-by-value的语义更为理想.是的,未使用的参数只是被遗忘了,而且参数太少也不会引发任何自动错误 - 变量只包含undef.您可以添加任意验证,例如通过检查大小@_.


有计划最终在将来提供命名参数,看起来像

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

如果安装模块,今天就可以使用此语法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
}
Run Code Online (Sandbox Code Playgroud)

这也支持实验类型检查.

解析器扩展FTW!


Dav*_*oss 6

如果你真的想在Perl中强加更严格的参数检查,你可以看看像Params :: Validate这样的东西.


vol*_*ron 5

Perl 确实具有参数占位符的原型设计功能,您已经习惯了这种功能,但通常没有必要。

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

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

  • 向新 Perl 程序员推荐原型会适得其反。它们与其他语言中的原型太不同了,并没有真正提供真正的安全性,而是一种虚假的安全感。 (5认同)

Sac*_*gol 5

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

事实上,在使用旧标准 Perl 时,您需要手动限制传递给子例程的参数数量,例如使用以下内容:

sub greet_one {
    die "Too many arguments for subroutine" unless @_ <= 1;
    my $name = $_[0] || "Bruce";
    say "Hello, $name!";
}
Run Code Online (Sandbox Code Playgroud)

使用现代 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 };
}
Run Code Online (Sandbox Code Playgroud)

请注意,函数签名是在 Perl 5.20 中引入的,并且在 Perl 5.36 之前被认为是实验性的。因此,如果您使用该范围内的 Perl 版本,您可能需要禁用“experimental::signatures”类别的警告:

use feature 'signatures';
no warnings 'experimental::signatures';
Run Code Online (Sandbox Code Playgroud)