21

我有一组如下所示的子例程:

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

4 回答 4

38

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

要使用原型做到这一点,您可以使用sub name (&) {...}where&告诉 perl 函数的第一个参数是一个块(有或没有sub),或者是一个文字子例程\&mysub。原型指定了(&)一个且只有一个参数,如果你需要在代码块之后传递多个参数,你可以写成(&@)这意味着,一个代码块后跟一个列表。

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

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

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

要调用此子例程,您将编写:

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

(&@)允许您将参数编写为map/ like 块的原型grep仅在将高阶函数用作函数时才有效。如果您将其用作方法,则应省略原型并这样编写:

sub higher_order_method {
    my $self = shift;
    my $code = \&{shift @_};
    ...
    $code->() for @_;
}
...
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here');
于 2011-05-23T19:08:58.473 回答
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 book 充满了这种编程。

于 2011-05-23T18:07:42.510 回答
9

你想要&原型。

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

使

foo { ... } ...;

相当于

foo(sub { ... }, ...);
于 2011-05-23T18:40:44.403 回答
0

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

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

于 2018-07-06T17:27:16.370 回答