【问题标题】:How to create a Perl subroutine that accepts a block of code如何创建一个接受代码块的 Perl 子例程
【发布时间】:2011-05-23 17:59: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),它读取不同文件的内容并将数据提供给哈希引用。也许有比我想要完成的更聪明的事情去做。

【问题讨论】:

  • 一般来说,函数原型不是必需的,我觉得它们有点用处。
  • @jiggy 你能详细说明一下吗?
  • => 在这种情况下,使用($) 原型可能并不像您认为的那样。它确实意味着“给我一个论点”,但它也意味着“对该论点施加标量上下文”。因此,如果您有一个包含一个元素的数组,并称为foo_1 @array,那么foo_1 将传递数字1,这是数组中元素的计数。要真正获得第一个参数,您需要将其称为foo_1 $array[0]。如果您没有原型,那么您可以将其称为foo_1 @array,它会正常工作。
  • ... 一些 Perl 程序员将这种上下文强制称为“远距离动作”,因为函数调用本身并没有告诉您参数将在标量上下文中(这可能是难以找到错误的来源)。通常,您应该将 Perl 中的原型使用限制在您想要编写像内置函数一样解析的函数的时候(例如在我下面的回答中)。参数验证可以在运行时在子例程顶部使用类似@_ == 1 or die "function takes 1 argument" 的行来完成。
  • @Eric 好!我真的想对 foo_1 的参数强加一个“标量上下文”(如示例中唯一的 arg 是文件名),但我并不完全了解“仅用于内置函数之类的函数”部分。谢谢。

标签: perl arguments subroutine


【解决方案1】:

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

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

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

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

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

要调用这个子程序,你可以这样写:

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

(&amp;@) 原型可让您将参数编写为 map/grep 类似块,仅在将高阶函数用作函数时才有效。如果你把它作为一个方法使用,你应该省略原型,这样写:

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

【讨论】:

  • ...继续(我按错了输入)我想知道您为什么使用 \&{shift @_}。为什么这与人们给我的其他答案不同?我对 Perl 还很陌生(但我已经用 Haskell、Java、Scheme 和 C 编写过一些代码)。谢谢。
  • 这是一种验证参数是否实际上是代码引用的简洁方法。扩展它意味着类似 do {my $x = shift; ref $x eq 'CODE' ? $x : overload::Overloaded($_[0], '&amp;{}') ? \&amp;$x : die "not a code reference"} 。原型是为您检查代码引用的编译时间约束,但可以通过使用&amp; sigil 调用子程序(或通过将代码作为方法调用)来绕过它。 \&amp;{shift @_} 是针对绕过的额外检查。
  • 我又读了几遍你的答案,现在我明白了。谢谢。
  • 太棒了!非常感谢你,埃里克。你上面的评论直到我写完我的评论才显示出来。这是我想象的。再次感谢!
  • 即使您将您的答案与高阶列表操作函数混为一谈,但我还是选择了您的最佳答案。谢谢。
【解决方案2】:
sub bar {
   my ($coderef) = @_;
   ⁝
   $coderef->($f, @arguments);
   ⁝
}

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

或者也许少一点与命名代码参考的纠缠:

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

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

【讨论】:

  • 非常感谢。我想我会读那本书。听起来很有趣!
【解决方案3】:

您想要&amp; 原型。

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

制作

foo { ... } ...;

相当于

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2015-01-18
      • 2019-06-23
      • 1970-01-01
      • 1970-01-01
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多