19

I'm just curious why one would choose to use an anonymous subroutine, versus a named one, in Perl. Thanks.

4

7 回答 7

35
  • 您可以将匿名 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()表现出前面演示的“意外”行为。

于 2009-05-07T13:20:52.697 回答
16

回调和生成器浮现在脑海中。一个例子:

#!/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
于 2009-05-07T13:27:38.513 回答
8

我讨论了匿名子例程以及为什么要在Mastering Perl中使用它们。简而言之,您开始将行为视为另一种形式的数据,就像您考虑字符串或数字一样。当您对这个想法感到满意时,您可以做一些非常了不起的事情,因为您可以将很多决定推迟到程序的最后阶段,并且您不必为了提前处理所有情况而扭曲代码设计可能会出现。

您可以在知道要运行子程序的情况下编写代码,只是您还不知道是哪一个。您相信前面的步骤可以为您解决这个问题。一旦你能做到这一点,你就处于另一个编程水平,感觉就像你正在编写代码来为你创建程序。通过这种方式,一些编程问题变得更容易解决。

而且,与其他所有功能一样,您可能会做得过火或不恰当地使用它。

于 2009-05-09T19:56:52.513 回答
8

“匿名”子例程与常规的命名子例程非常相似,只是它们没有绑定到符号表中的名称。

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 中函数式编程的各种技术和应用。

于 2009-05-07T14:27:10.383 回答
4

匿名子程序的规范答案通常是数组的数字排序:

my @sorted_array = sort { $a <=> $b } @array;

{ $a <=> $b }代表一个匿名子例程。

于 2009-05-07T13:42:56.463 回答
1

第一:子事物是子。我的 $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] 一样。

于 2009-05-07T13:29:32.923 回答
0

这是我重写 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"
    );
  }
);

基本上我能想到的唯一原因是回调或跳转表。

于 2009-05-07T18:17:16.447 回答