Подтвердить что ты не робот

Как создать подпрограмму Perl, которая принимает блок кода

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

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 представляют собой другую машину конечного состояния (FMS), которая считывает содержимое разных файлов и передает данные в хеш-ссылку. Возможно, есть более разумная вещь, чем то, что я пытаюсь выполнить.

4b9b3361

Ответ 1

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

Ответ 2

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

Изменить: Книга более высокого порядка Perl полна такого рода программирования.

Ответ 3

Вам нужен прототип &.

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

марки

foo { ... } ...;

эквивалентно

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