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

如何创建接受代码块的Perl子例程,perl,arguments,subroutine,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(); } sub foo_1($){ 我的$name=shi

我有一组子程序,如下所示:

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();

}
sub foo_1($){
我的$name=shift;
我的$f;
跑什么;
打开($f,$name)或死亡(“无法打开$name”);
而(){
//给福乌的东西1()
}
结束(f美元);
做点别的事吧;
}
我有四个或更多看起来一样的,唯一改变的是while块的主体。我想将其抽象出来并停止复制粘贴代码

  • 有没有办法编写一个接受代码块并执行它的子程序
为了提供更多的上下文,不同的foo子例程是一个不同的有限状态机(FSM),它读取不同文件的内容并将数据提供给散列引用。也许有一件事比我想完成的更聪明

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);


编辑:这本书充满了这种编程。

你想要
&
原型

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

相当于

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

Perl提供了一个名为subroutine Prototype的系统,允许您编写用户Sub,并以类似于内置函数的方式对其进行解析。要模拟的内置项有
map
grep
sort
,它们都可以将一个块作为第一个参数

对于原型,您可以使用
子名称(&){…}
,其中
&
告诉perl函数的第一个参数是一个块(带或不带
sub
),或者是一个文本子例程
\&mysub
(&)
原型只指定一个参数,如果需要在代码块之后传递多个参数,可以将其写成
(&@)
,这意味着代码块后面是一个列表

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

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

要调用此子例程,请编写:

higher_order_fn {$_ * 2} 1, 2, 3;
# or
higher_order_fn(sub {$_ * 2}, 1, 2, 3);
(&@)
原型允许您将参数编写为类似于
映射的
/
grep
块,它仅在将高阶函数用作函数时起作用。如果将其用作一种方法,则应省略原型,并按以下方式编写:

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

尽管其他人已经回答了这个问题,但我仍然没有提到官方的Perl文档


一般来说,函数原型是不必要的,我觉得它们有点没用。@jiggy你能详细说明一下吗?=>在这种情况下,使用
($)
原型可能不是你认为的意思。它确实意味着“给我一个参数”,但它也意味着“在那个参数上施加标量上下文”。因此,如果您有一个包含一个元素的数组,并调用
foo_1@array
,那么
foo_1
将被传递数字
1
,这是数组中元素的计数。要实际获取第一个参数,需要将其称为
foo\u 1$array[0]
。如果您没有原型,那么您可以将其称为
foo_1@array
,它会正常工作。。。。一些Perl程序员将这种上下文强制称为“远距离操作”,因为函数调用本身并没有告诉您参数将在标量上下文中(这可能是难以找到的bug的来源)。一般来说,您应该将Perl中的原型使用限制在您希望编写像内置函数一样解析的函数的时候(如下面我的回答)。参数验证可以在运行时使用类似于子例程顶部的
@\u==1或die“function takes 1 Argument”
的行来完成。@Eric Good!我真的想给foo_1的参数加上一个“标量上下文”(在示例中,唯一的参数是文件名),但我并不完全了解“仅适用于内置函数”部分。谢谢你…继续(我误按回车键)我想知道你为什么使用\&{shift@}。为什么这与人们给我的其他答案不同?我对Perl非常陌生(但我已经用Haskell、Java、Scheme和C编写了一些代码)。谢谢。这是一种简洁的方法来验证参数是否确实是代码引用。扩展它的意思是类似于
do{my$x=shift;ref$x eq'code'?$x:重载::重载($\[0],'&{}')?\&$x:die“不是代码引用”}
。原型是一个编译时约束,用于为您检查代码引用,但可以通过使用
&
符号调用sub(或将代码作为方法调用)绕过原型。
\&{shift@}
是对该旁路的额外检查。我又读了几遍你的答案,现在我明白了。谢谢,太好了!非常感谢你,埃里克。直到我写了我的评论之后,你上面的评论才出现。这是我想象的。再次感谢!尽管你把答案和高阶列表操作函数混在了一起,我还是选择了你的答案。谢谢,非常感谢。我想我会读那本书。听起来很有趣!
sub higher_order_method {
    my $self = shift;
    my $code = \&{shift @_};
    ...
    $code->() for @_;
}
...
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here');