探索匿名潜艇的用途

时间:2011-06-30 13:55:57

标签: perl

我一直对perl中匿名subs的用途和用法感到有些困惑。我理解这个概念,但是寻找这个实践的价值的例子和解释。

要明确:

sub foo { ... }   # <--- named sub
sub { ... }       # <--- anonymous sub

例如:

$ perl -e 'print sub { 1 }'
CODE(0xa4ab6c)

告诉我sub返回一个标量值。所以,我可以这样做:

$ perl -e '$a = sub { 1 }; print $a'

对于与上面相同的输出。这当然适用于所有标量值,因此您可以使用匿名子加载数组或哈希值。

问题是,我如何使用这些潜艇?我为什么要使用它们?

对于一个金星来说,是否有任何问题只能通过匿名子解决?

8 个答案:

答案 0 :(得分:10)

匿名子程序可用于各种事物。

  1. 事件处理系统的回调:

    my $obj = Some::Obj->new;
    
    $obj->on_event(sub {...});
    
  2. 迭代器:

    sub stream {my $args = \@_; sub {shift @$args}}
    
    my $s = stream 1, 2, 3;
    
    say $s->(); # 1
    say $s->(); # 2 
    
  3. 高阶函数:

    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'
    
  4. 创建别名数组:

    my $alias = sub {\@_}->(my $x, my $y);
    
    $alias[0]++;
    $alias[1] = 5;
    
    say "$x $y";  # '1 5''
    
  5. 使用闭包进行动态编程(例如创建一堆只有少量不同的子程序):

    for my $name (qw(list of names)) {
        no strict 'refs';
        *$name = sub {... something_with($name) ...};
    }
    
  6. 没有匿名子例程可以执行命名子例程不能执行的任何操作的情况。 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访问,因此它们在符号表中没有条目。