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


person Ivan Salazar    schedule 23.05.2011    source источник
comment
Вообще говоря, прототипы функций не нужны, и я считаю их бесполезными.   -  person jiggy    schedule 23.05.2011
comment
@jiggy Не могли бы вы немного уточнить, пожалуйста?   -  person Ivan Salazar    schedule 24.05.2011
comment
=› В этом случае использование прототипа ($) может означать не то, что вы думаете. Это означает дать мне один аргумент, но это также означает наложение скалярного контекста на этот аргумент. Таким образом, если у вас есть массив с одним элементом в нем, и вы вызвали foo_1 @array, то foo_1 будет передано число 1, которое является количеством элементов в массиве. Чтобы на самом деле получить первый аргумент, вам нужно будет вызвать его как foo_1 $array[0]. Если бы у вас не было прототипа, то вы могли бы назвать его как foo_1 @array и он работал бы правильно.   -  person Eric Strom    schedule 24.05.2011
comment
... Некоторые программисты Perl называют это наложением контекстного действия на расстоянии, поскольку в самом вызове функции нет ничего, что говорило бы вам, что аргумент будет в скалярном контексте (и это может быть источником трудно найти ошибки). В общем, вы должны ограничить использование прототипа в Perl теми случаями, когда вы хотите написать функцию, которая анализируется как встроенная функция (например, в моем ответе ниже). Проверка аргумента может быть выполнена во время выполнения с помощью строки типа @_ == 1 or die "function takes 1 argument" в верхней части подпрограммы.   -  person Eric Strom    schedule 24.05.2011
comment
@Эрик Хорошо! Я действительно хотел навязать скалярный контекст аргументу foo_1 (поскольку в примере единственным аргументом было имя файла), но я не полностью осознавал только функции for, такие как встроенная часть. Спасибо.   -  person Ivan Salazar    schedule 24.05.2011


Ответы (4)


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');
person Eric Strom    schedule 23.05.2011
comment
...продолжение (по ошибке нажал Enter) Я хотел бы знать, почему вы используете \&{shift @_}. Почему это отличается от других ответов, которые мне дали люди? Я новичок в Perl (но я некоторое время программировал на Haskell, Java, Scheme и C). Спасибо. - person Ivan Salazar; 24.05.2011
comment
это краткий способ проверки того, что аргумент на самом деле является ссылкой на код. в расширенном виде это означает что-то вроде do {my $x = shift; ref $x eq 'CODE' ? $x : overload::Overloaded($_[0], '&{}') ? \&$x : die "not a code reference"} . Прототип — это ограничение времени компиляции, которое проверяет наличие ссылки на код для вас, но его можно обойти, вызвав подпрограмму с сигилом & (или вызвав код как метод). \&{shift @_} является дополнительной проверкой этого обхода. - person Eric Strom; 24.05.2011
comment
Я перечитал ваш ответ еще пару раз, и теперь я понимаю. Спасибо. - person Ivan Salazar; 24.05.2011
comment
Отлично! Большое спасибо, Эрик. Ваш комментарий выше не показывался, пока я не написал свой. Это было то, что я себе представлял. Еще раз спасибо! - person Ivan Salazar; 24.05.2011
comment
Несмотря на то, что вы как бы смешали свой ответ с функциями манипулирования списками более высокого порядка, я выбрал ваш как лучший. Спасибо. - person Ivan Salazar; 24.05.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);

Правка: книга Higher-Order Perl полна подобного программирования. .

person daxim    schedule 23.05.2011
comment
Большое Вам спасибо. Думаю, я прочитаю эту книгу. Звучит смешно! - person Ivan Salazar; 24.05.2011

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

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

делает

foo { ... } ...;

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

foo(sub { ... }, ...);
person ikegami    schedule 23.05.2011

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

http://perldoc.perl.org/perlsub.html#Прототипы

person 3limin4t0r    schedule 06.07.2018