Как создать подпрограмму Perl, которая принимает блок кода - PullRequest
18 голосов
/ 23 мая 2011

У меня есть набор подпрограмм, которые выглядят так:

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 ]

36 голосов
/ 23 мая 2011

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 ref, то оно возвращается без изменений. Если это был перегруженный объект, он превращается в код. Если это не может быть приведено в 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');
12 голосов
/ 23 мая 2011
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);

Редактировать : Книга Perl высшего порядка *1009* полна такого рода программирования.

9 голосов
/ 23 мая 2011

Требуется прототип &.

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

делает

foo { ... } ...;

эквивалентным

foo(sub { ... }, ...);
0 голосов
/ 06 июля 2018

Хотя другие уже ответили на вопрос, мне все еще не хватало ссылки на официальную документацию Perl.

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

...