[英]Perl subroutine arguments
我最近一直在阅读有关 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 } }
您对 Perl 环境持谨慎态度,因为它与您之前遇到的语言大不相同。
相信强类型和函数原型的人在这里会不同意,但我相信这样的限制很少有用。 C 是否真的发现您经常向函数传递错误数量的参数以致于有用?
在现代 Perl 中最常见的是将@_
的内容复制到词法标量变量列表中,因此您经常会看到子程序以
sub mysub {
my ($p1, $p2) = @_;
... etc.
}
这样,所有传递的参数都可以作为@_
( $_[0]
、 $_[1]
等)的元素使用,而预期的参数被命名并出现在$p1
和$p2
(尽管我希望你理解应该适当地选择这些名称)。
在子程序是方法的特殊情况下,第一个参数是特殊的。 在其他语言中,它是self
或this
,但在 Perl 中,它只是@_
的第一个参数,您可以@_
称呼它。 在这些情况下你会看到
sub method {
my $self = shift;
my ($p1, $p2) = @_;
... etc.
}
以便上下文对象(或类的名称,如果它是类方法)被提取到$self
(约定假定的名称)中,其余参数保留在@_
以便直接访问,或者更常见的是, 复制到局部标量变量$p1
, $p2
等。
最常见的抱怨是也没有类型检查,所以我可以传递任何我喜欢的标量作为子程序参数。 只要use strict
和use warnings
在上下文中,即使这样通常也很容易调试,仅仅是因为子例程可以对一种形式的标量执行的操作在另一种形式上通常是非法的。
虽然它最初更多地与面向对象 Perl 的封装有关,但 Larry Wall 的这句话非常相关
Perl 不迷恋强制隐私。 它宁愿你呆在客厅外面,因为你没有被邀请,而不是因为它有一把猎枪
C 的设计和实现是在那个时代,如果您可以让错误的程序在编译期间而不是在运行时失败,它会大大提高效率。 现在已经改变,虽然类似的情况已经与客户端JavaScript出现在那里,它实际上是知道的代码是从它必须处理与互联网获取数据之前错误有用。 可悲的是,JavaScript 参数检查现在比它应该的要松散。
更新
对于那些怀疑 Perl 用于教学目的的人,我建议正是因为Perl 的机制非常简单和直接,所以它们非常适合此类目的。
当您调用 Perl 子例程时,调用中的所有参数都以@_
为别名。 您可以直接使用它们来影响实际参数,或者复制它们以防止外部动作
如果您将 Perl 子例程作为方法调用,则调用对象或类将作为第一个参数提供。 同样,子程序(方法)可以用@_
做它喜欢做的事
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 喜欢列表,而不喜欢静态类型。 @_
数组实际上提供了很大的灵活性,因为子程序参数是通过引用传递的,而不是通过值传递。 例如,这允许我们做out-arguments:
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!
如果您真的想在 Perl 中强加更严格的参数检查,您可以查看类似Params::Validate 的内容。
Perl 确实具有参数占位符的原型设计功能,您已经习惯了这种功能,但通常没有必要。
sub foo($){
say shift;
};
foo(); # Error: Not enough arguments for main::foo
foo('bar'); # executes correctly
如果你做了sub foo($$){...}
它将需要 2 个非可选参数(例如foo('bar','baz')
)
你可以只使用:
my ($arg1, $arg2) = @_;
要明确限制您可以使用的参数数量:
my $number =2;
die "Too many arguments" if @_ > $number;
如果您最近正在阅读 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 };
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.