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

use*_*881 19 perl anonymous subroutine

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

inn*_*naM 33

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

最后一点可能是最重要的,因为它通常是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!
Run Code Online (Sandbox Code Playgroud)

但是将"内部"从命名子例程更改为对匿名子例程的引用,并且它的工作方式不那么令人惊讶:

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
Run Code Online (Sandbox Code Playgroud)

(当然,每个人的期望都不同,因此"恐慌报价"围绕"预期".)

这是在实际代码中使用的一个示例(尽管应该注意,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;
}
Run Code Online (Sandbox Code Playgroud)

传递一个命名子作为价值wanted参数需要污染命名空间与可能只能使用一次例行公事,定义了一个名为子程序find_files()子程序会表现出"意外"的行为早些时候证实.


Sin*_*nür 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
Run Code Online (Sandbox Code Playgroud)


xdg*_*xdg 8

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

sub Foo { stuff() }

BEGIN { *Foo = sub { stuff() } }  # essentially equivalent
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,创建"匿名"子例程,然后将其绑定到当前名称空间中的名称"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'
Run Code Online (Sandbox Code Playgroud)

有关匿名子程序有用的原因,我推荐使用High Order Perl这本书来描述Perl中函数式编程的各种技术和应用.


bri*_*foy 8

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

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

并且,与其他所有功能一样,您可以将此操作过度使用或不恰当地使用它.