我一直对perl中匿名subs的用途和用法感到有些困惑。我理解这个概念,但是寻找这个实践的价值的例子和解释。
要明确:
sub foo { ... } # <--- named sub
sub { ... } # <--- anonymous sub
例如:
$ perl -e 'print sub { 1 }'
CODE(0xa4ab6c)
告诉我sub返回一个标量值。所以,我可以这样做:
$ perl -e '$a = sub { 1 }; print $a'
对于与上面相同的输出。这当然适用于所有标量值,因此您可以使用匿名子加载数组或哈希值。
问题是,我如何使用这些潜艇?我为什么要使用它们?
对于一个金星来说,是否有任何问题只能通过匿名子解决?
答案 0 :(得分:10)
匿名子程序可用于各种事物。
事件处理系统的回调:
my $obj = Some::Obj->new;
$obj->on_event(sub {...});
迭代器:
sub stream {my $args = \@_; sub {shift @$args}}
my $s = stream 1, 2, 3;
say $s->(); # 1
say $s->(); # 2
高阶函数:
sub apply (&@) {
my $code = shift;
$code->() for my @ret = @_;
@ret
}
my @clean = apply {s/\W+/_/g} 'some string', 'another string.';
say $clean[0]; # 'some_string'
创建别名数组:
my $alias = sub {\@_}->(my $x, my $y);
$alias[0]++;
$alias[1] = 5;
say "$x $y"; # '1 5''
使用闭包进行动态编程(例如创建一堆只有少量不同的子程序):
for my $name (qw(list of names)) {
no strict 'refs';
*$name = sub {... something_with($name) ...};
}
没有匿名子例程可以执行命名子例程不能执行的任何操作的情况。 my $ref = sub {...}
构造函数等效于以下内容:
sub throw_away_name {...}
my $ref = \&throw_away_name;
无需为每个子目录决定唯一的'throw_away_name'而烦恼。
等价也是另一种方式,sub name {...}
等同于:
BEGIN {*name = sub {...}}
除了名称之外,任一方法创建的代码引用都是相同的。
要调用子例程引用,可以使用以下任何一个:
$code->(); # calls with no args
$code->(1, 2, 3); # calls with args (1, 2, 3)
&$code(); # calls with no args
&$code; # calls with whatever @_ currently is
您甚至可以使用代码引用作为有福或无法使用的标量的方法:
my $list = sub {@{ $_[0] }};
say for [1 .. 10]->$list # which prints 1 .. 10
答案 1 :(得分:8)
您可以使用它来创建迭代器。
use strict;
use warnings;
use 5.012;
sub fib_it {
my ($m, $n) = (0, 0);
return sub {
my $val = ( $m + $n );
$val = 1 unless $val;
($m, $n) = ($n, $val);
return $val;
}
}
my $fibber = fib_it;
say $fibber->() for (1..3); ### 1 1 2
my $fibber2 = fib_it;
say $fibber2->() for (1..5); ### 1 1 2 3 5
say $fibber->() for (1..3); #### 3 5 8
答案 2 :(得分:8)
匿名子程序可用于创建闭包。
Closure是Lisp世界中的一个概念,它说如果你在特定的词汇上下文中定义一个匿名函数,它就会假装在该上下文中运行,即使它在上下文之外被调用。
答案 3 :(得分:7)
以下是您可能见过的类似内容:
@new_list = map { $_ + 1 } @old_list;
还有:
@sorted = sort { $a <=> $b } @unsorted;
这些都不是匿名潜艇,但是他们的行为可以在你的匿名潜艇的功能中被模仿。它们不需要sub
关键字,因为函数(基本上)是原型,以使它们的第一个参数成为子例程,而Perl认为这是一个特殊情况,sub
可以保留。 (函数还会在调用您提供的子例程之前将必需变量设置为有意义的值,以简化参数传递,但这不相关。)
您可以编写自己的map
- 类似功能:
sub mapgrep (&@) { # make changes and also filter based on defined-ness
my ($func, @list) = @_;
my @new;
for my $i (@list) {
my $j = $func->($i);
push @new, $j if defined $j;
}
}
让它与$ _一起工作的魔力在这里写得有点多 - 上面的版本仅适用于带参数的子。
答案 4 :(得分:4)
我为perl编写了一个SAX解析器,它是事件驱动的。您可以将匿名subs传递给元素上的begin / end事件。
my $str = "<xml><row><data></data></row></xml>":
my $parser = SAXParser->new();
$parser->when('row')->begin(sub {
my ($element) = @_;
push(@rows, $row);
});
$parser->when('row')->end(sub {
## do something like serialize it or whatever
});
$parser->parse($str);
答案 5 :(得分:3)
当您想要将sub传递给另一位代码时,通常会使用它们。通常这是“当X发生时(在第三方代码中)做Y”的情况。
例如。在Moose中定义属性时,可以使用sub指定该属性的默认值。给定一个类,作为其定义的一部分:
has 'size' => (
is => 'ro',
default =>
sub { ( 'small', 'medium', 'large' )[ int( rand 3 ) ] },
predicate => 'has_size',
);
每当创建该类的实例而没有传递显式大小时,将调用该子,并且返回值将是该对象的大小。
如果我们切换到另一种语言来提供不同的示例,您将在JavaScript中找到类似的概念。
var b = document.getElementById('my_button').
b.addEventListener('click', function (e) { alert('clicked!'); });
答案 6 :(得分:2)
在您的示例中,您实际上没有调用创建的子例程。使用&amp; $ a或$ a-&gt;()语法执行呼叫。您所做的是您将引用存储到$ a中的子例程中,然后对其进行字符串化并打印结果。比较:
my $a = sub {1};
my $b = sub {1};
print join("\n", $a, $a->(), $b, $b->());
答案 7 :(得分:2)
这些是懒惰程序员的潜艇。您可以将它们用于本地丢弃功能,并可以节省一些打字。而不是
sub x { ... }
my $function_ptr = \&x;
您现在可以使用
my $function_ptr = sub { ... };
匿名函数也是私有函数,只能通过$function_ptr
访问,因此它们在符号表中没有条目。