I'm just curious why one would choose to use an anonymous subroutine, versus a named one, in Perl. Thanks.
7 回答
- 您可以将匿名 subs 存储在数组、散列和标量中。
- 您可以在运行时构建它们
- 您可以将它们作为参数传递给其他函数。
- 您可以将变量保留在周围范围内。
最后一点可能是最重要的,因为它通常是 Perl 中命名子例程与匿名子例程最出人意料的方面。例子:
sub outer
{
my $a = 123;
sub inner
{
print $a, "\n";
}
# At this point, $a is 123, so this call should always print 123, right?
inner();
$a = 456;
}
outer(); # prints 123
outer(); # prints 456! Surprise!
但是将“内部”从命名子例程更改为对匿名子例程的引用,并且它的工作方式并不令人惊讶:
sub outer
{
my $a = 123;
my $inner = sub
{
print $a, "\n";
};
# At this point, $a is 123, and since the anonymous subrotine
# whose reference is stored in $inner closes over $a in the
# "expected" way...
$inner->();
$a = 456;
}
# ...we see the "expected" results
outer(); # prints 123
outer(); # prints 123
(当然,每个人的期望都不一样,所以“期望”周围的“恐吓引号”。)
这是一个在实际代码中使用的示例(尽管应该注意,该File::Find
接口通常被认为是一个糟糕的接口——由于它使用全局变量,而不是使用匿名子例程):
sub find_files
{
my @files;
my $wanted = sub
{
if($something)
{
push @files, $File::Find::name;
}
};
# The find() function called here is imported from File::Find
find({ wanted => $wanted }, $directory);
return @files;
}
将命名子例程作为wanted
参数值传递将需要使用只能使用一次的例程来污染名称空间,并且在子例程中定义命名子例程将find_files()
表现出前面演示的“意外”行为。
回调和生成器浮现在脑海中。一个例子:
#!/usr/bin/perl
use strict;
use warnings;
sub generate_multiplier {
my ($coef) = @_;
return sub {
my ($val) = @_;
$coef * $val;
}
}
my $doubler = generate_multiplier(2);
my $tripler = generate_multiplier(3);
for my $i ( 1 .. 10 ) {
printf "%4d%4d%4d\n", $i, $doubler->($i), $tripler->($i);
}
__END__
C:\Temp> v
1 2 3
2 4 6
3 6 9
4 8 12
5 10 15
6 12 18
7 14 21
8 16 24
9 18 27
10 20 30
我讨论了匿名子例程以及为什么要在Mastering Perl中使用它们。简而言之,您开始将行为视为另一种形式的数据,就像您考虑字符串或数字一样。当您对这个想法感到满意时,您可以做一些非常了不起的事情,因为您可以将很多决定推迟到程序的最后阶段,并且您不必为了提前处理所有情况而扭曲代码设计可能会出现。
您可以在知道要运行子程序的情况下编写代码,只是您还不知道是哪一个。您相信前面的步骤可以为您解决这个问题。一旦你能做到这一点,你就处于另一个编程水平,感觉就像你正在编写代码来为你创建程序。通过这种方式,一些编程问题变得更容易解决。
而且,与其他所有功能一样,您可能会做得过火或不恰当地使用它。
“匿名”子例程与常规的命名子例程非常相似,只是它们没有绑定到符号表中的名称。
sub Foo { stuff() }
BEGIN { *Foo = sub { stuff() } } # essentially equivalent
在第二种情况下,“匿名”子例程被创建,然后绑定到当前命名空间中的名称“Foo”。BEGIN 块使它在编译时发生,就像处理命名子例程一样。(它有点复杂,因为第一种情况给它一个名称,该名称将显示在堆栈跟踪中。)
当您想在运行时创建函数时,匿名子例程很有用。这对于“闭包”特别有用——“记住”其词法上下文的函数。例如,将列表转换为迭代器:
use 5.010;
use strict;
use warnings;
sub make_iterator {
my @list = @_;
return sub { shift @list }; # new sub that 'remembers' @list
}
my $iter1 = make_iterator( 0 .. 10 );
my $iter2 = make_iterator( 'a' .. 'z' );
say $iter1->(); # '0'
say $iter1->(); # '1'
say $iter2->(); # 'a'
关于为什么匿名子程序有用的更多信息,我推荐《高阶 Perl 》一书,它描述了 Perl 中函数式编程的各种技术和应用。
匿名子程序的规范答案通常是数组的数字排序:
my @sorted_array = sort { $a <=> $b } @array;
{ $a <=> $b }
代表一个匿名子例程。
第一:子事物是子。我的 $thing=sub... 是存储在变量中的子引用。
第二:有一个微妙的用法差异:
use strict;
use warnings;
sub xx {
my $zz=1;
sub yy {
print $zz;
}
}
perl tmp.pl
Variable "$zz" will not stay shared at tmp.pl line 8.
将 [ ...]更改sub yy
为 [ my $yy = sub {
...] 或 [ local *yy = sub{
...],投诉就会消失。
而且,老实说,对子的引用更容易处理,就像@x=(1,2,3) 与 $x=[1, 2, 3] 一样。
这是我重写 Nasm 的 version.pl的一个例子
# jump table to subroutines / variables
my %jump = (
id => 'id',
xid => 'xid',
hex_id => 'xid',
h => \&h,
mac => \&mac,
sed => \&sed,
make => \&make,
nsis => \&nsis,
perl => \&perl,
dump => \&perl,
yaml => \&yaml,
yml => \&yaml,
json => \&json,
js => \&json,
help => \&help,
usage => sub{
require Pod::Usage;
Pod::Usage::pod2usage(
"run perldoc $0 or pod2text $0 for more information"
);
}
);
基本上我能想到的唯一原因是回调或跳转表。