我有一组看起来像这样的子程序:
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),它读取不同文件的内容并将数据提供给哈希引用。也许有比我想要完成的事情更聪明的事情。
答案 0 :(得分:36)
Perl提供了一个称为子例程原型的系统,允许您编写以类似于内置函数的方式解析的用户子。您要模拟的内置素是map
,grep
或sort
,每个内容都可以将块作为第一个参数。
要使用原型执行此操作,可以使用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官方文档的引用。