为什么我要使用Perl匿名子程序而不是命名子程序?

时间:2009-05-07 13:15:33

标签: perl anonymous subroutine

我只是好奇为什么会选择在Perl中使用匿名子程序而不是命名子程序。感谢。

7 个答案:

答案 0 :(得分:31)

  • 您可以将匿名子存储在数组,散列和标量中。
  • 您可以在运行时构建它们
  • 您可以将它们作为参数传递给其他函数。
  • 您可以将变量保留在周围范围内。

最后一点可能是最重要的,因为它通常是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接口通常被认为是一个很差的接口 - 由于它使用全局变量,而不是使用匿名子程序):< / p>

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参数的值传递将需要使用只能使用一次的例程来污染命名空间,并在 {{1}中定义命名子例程子例程将展示先前演示的“意外”行为。

答案 1 :(得分:15)

回想起回调和生成器。一个例子:

#!/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

答案 2 :(得分: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'

关于为什么匿名子程序有用的更多内容,我推荐书籍Higher Order Perl,它描述了Perl中函数式编程的各种技术和应用。

答案 3 :(得分:8)

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

你可以编写代码,知道你要运行一个子程序,只有你还不知道哪一个。您相信以前的步骤可以为您解决这个问题。一旦你能做到这一点,你就会在另一个级别的编程中感觉就像是在编写代码来为你创建程序。一些编程问题变得更容易以这种方式解决。

而且,和其他所有功能一样,你可以把它拿得太远或者使用不当。

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:1)

首先:子东西是一个子。 my $ 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{ ...],投诉就会消失。

另外,说实话,引用sub更容易处理,与@ x =(1,2,3)与$ x = [1,2,3]相同。

答案 6 :(得分:0)

以下是我rewrite of Nasm's 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"
    );
  }
);

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