如何创建一个接受代码块的Perl子例程

时间:2011-05-23 17:59:21

标签: perl arguments subroutine

我有一组看起来像这样的子程序:

sub foo_1($) {
  my $name = shift;
  my $f; 

  run_something();
  open($f, $name) or die ("Couldn't open $name");
  while (<$f>) {
    //Something for foo_1()
  }
  close($f); 
  do_something_else();

}

我有四个或更多看起来相同,唯一改变的是while块的主体。我想抽象一下并停止复制粘贴代码。

  • 有没有办法编写一个接受代码块并执行它的子程序?

为了给出更多的上下文,不同的foo子程序是一个不同的有限状态机(FSM),它读取不同文件的内容并将数据提供给哈希引用。也许有比我想要完成的事情更聪明的事情。

4 个答案:

答案 0 :(得分:36)

Perl提供了一个称为子例程原型的系统,允许您编写以类似于内置函数的方式解析的用户子。您要模拟的内置素是mapgrepsort,每个内容都可以将块作为第一个参数。

要使用原型执行此操作,可以使用sub name (&) {...} &告诉perl函数的第一个参数是块(带或不带sub)或文字子程序\&mysub(&)原型指定一个且只有一个参数,如果需要在代码块之后传递多个参数,则可以将其写为(&@),这意味着代码块后跟一个列表。

sub higher_order_fn (&@) {
    my $code = \&{shift @_}; # ensure we have something like CODE

    for (@_) {
        $code->($_);
    }
}

该子例程将在传入列表的每个元素上运行传入的块。 \&{shift @_}看起来有点神秘,但它的作用是移出列表的第一个元素,它应该是一个代码块。 &{...}将值取消引用作为子例程(调用任何重载),然后\立即获取对它的引用。如果值是CODE引用,则返回不变。如果它是一个重载的对象,它将变成代码。如果无法强制转换为CODE,则会抛出错误。

要调用此子例程,您可以写:

higher_order_fn {$_ * 2} 1, 2, 3;
# or
higher_order_fn(sub {$_ * 2}, 1, 2, 3);

允许您将参数写为(&@) / map类似块的grep原型仅在使用高阶函数作为函数时起作用。如果您将它用作方法,则应省略原型并以这种方式编写它:

sub higher_order_method {
    my $self = shift;
    my $code = \&{shift @_};
    ...
    $code->() for @_;
}
...
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here');

答案 1 :(得分:12)

sub bar {
   my ($coderef) = @_;
   ⁝
   $coderef->($f, @arguments);
   ⁝
}

bar(sub { my ($f) = @_; while … }, @other_arguments);

或者可能与命名的coderef纠缠不清:

my $while_sub = sub {
    my ($f) = @_;
    while …
    ⁝
};
bar($while_sub, @other_arguments);

编辑Higher-Order Perl本书充满了这种编程。

答案 2 :(得分:9)

你想要&原型。

sub foo(&@) {
    my ($callback) = shift;
    ...
    $callback->(...);
    ...
}

品牌

foo { ... } ...;

相当于

foo(sub { ... }, ...);

答案 3 :(得分:0)

尽管其他人已经回答了这个问题,但我仍然缺少对Perl官方文档的引用。

http://perldoc.perl.org/perlsub.html#Prototypes