#perl #arguments #subroutine
#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), которые считывают содержимое разных файлов и передают данные в хэш-ссылку. Возможно, есть более разумная вещь, которую можно сделать, чем то, что я пытаюсь выполнить.
Комментарии:
1. Вообще говоря, прототипы функций не нужны, и я нахожу их отчасти бесполезными.
2. @jiggy Не могли бы вы немного уточнить, пожалуйста?
3. => В этом случае использование
($)
прототипа может означать не то, что вы думаете. Это действительно означает «дайте мне один аргумент», но это также означает «наложить скалярный контекст на этот аргумент». Итак, если бы у вас был массив с одним элементом в нем, и вы вызывали бы егоfoo_1 @array
, тогдаfoo_1
было бы передано число1
, которое является количеством элементов в массиве. Чтобы фактически получить первый аргумент, вам нужно было бы вызвать его какfoo_1 $array[0]
. Если бы у вас не было прототипа, то вы могли бы вызвать его какfoo_1 @array
, и это сработало бы должным образом.4. … Некоторые программисты на Perl называют это наложение контекста «действием на расстоянии», поскольку в самом вызове функции нет ничего, что указывало бы на то, что аргумент будет находиться в скалярном контексте (и это может быть источником трудно обнаруживаемых ошибок). В общем, вам следует ограничить использование прототипа в Perl теми случаями, когда вы хотите написать функцию, которая анализируется как встроенная функция (например, в моем ответе ниже). Проверка аргументов может быть выполнена во время выполнения с помощью строки типа
@_ == 1 or die "function takes 1 argument"
в верхней части подпрограммы.5. @Эрик Хорошо! Я действительно хотел наложить «скалярный контекст» на аргумент foo_1 (поскольку в примере единственным аргументом было имя файла), но я не был полностью осведомлен о части «только для функций, подобных встроенным». Спасибо.
Ответ №1:
Perl предлагает систему, называемую прототипами подпрограмм, которая позволяет вам писать пользовательские модули, которые анализируются способом, аналогичным встроенным функциям. Встроенными элементами, которые вы хотите эмулировать, являются map
, grep
или sort
, каждый из которых может принимать блок в качестве своего первого аргумента.
Чтобы сделать это с прототипами, вы используете, sub name (amp;) {...}
где amp;
указывается perl, что первым аргументом функции является либо блок (с или без sub
), либо литеральная подпрограмма amp;mysub
. (amp;)
Прототип указывает один и только один аргумент, если вам нужно передать несколько аргументов после блока кода, вы могли бы записать это как, (amp;@)
что означает, блок кода, за которым следует список.
sub higher_order_fn (amp;@) {
my $code = amp;{shift @_}; # ensure we have something like CODE
for (@_) {
$code->($_);
}
}
Эта подпрограмма будет запускать блок «передано в» для каждого элемента списка «Передано в». amp;{shift @_}
Выглядит немного загадочно, но то, что она делает, — это смещает первый элемент списка, который должен быть блоком кода. amp;{...}
Разыменовывает значение как подпрограмму (вызывая любую перегрузку), а затем
немедленно принимает ссылку на него. Если значением была ссылка на КОД, то оно возвращается без изменений. Если это был перегруженный объект, он превращается в код. Если его не удалось принудительно преобразовать в КОД, выдается ошибка.
Чтобы вызвать эту подпрограмму, вы должны написать:
higher_order_fn {$_ * 2} 1, 2, 3;
# or
higher_order_fn(sub {$_ * 2}, 1, 2, 3);
(amp;@)
Прототип, который позволяет записывать аргумент в виде map
/ grep
подобного блока, работает только при использовании функции более высокого порядка в качестве функции. Если вы используете ее как метод, вам следует опустить прототип и записать его таким образом:
sub higher_order_method {
my $self = shift;
my $code = amp;{shift @_};
...
$code->() for @_;
}
...
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here');
Комментарии:
1. …продолжение (я по ошибке нажал enter) Я хотел бы знать, почему вы используете amp;{shift @_}. Почему это отличается от других ответов, которые мне давали люди? Я довольно новичок в Perl (но я некоторое время кодировал на Haskell, Java, Scheme и C). Спасибо.
2. это краткий способ проверки того, что аргумент на самом деле является ссылкой на код. расширенное это означает что-то вроде
do {my $x = shift; ref $x eq 'CODE' ? $x : overload::Overloaded($_[0], 'amp;{}') ? amp;$x : die "not a code reference"}
. Прототип — это ограничение времени компиляции, которое проверяет наличие ссылки на код для вас, но его можно обойти, вызвав sub сamp;
символом (или вызвав код как метод).amp;{shift @_}
Является дополнительной проверкой на этот обход.3. Я еще пару раз перечитал ваш ответ и теперь понимаю. Спасибо.
4. Отлично! Большое тебе спасибо, Эрик. Ваш комментарий выше не появлялся до тех пор, пока я не написал свой. Это было то, что я себе представлял. Еще раз спасибо!
5. Несмотря на то, что вы немного перепутали свой ответ с функциями управления списками более высокого порядка, я выбрал ваш как лучший. Спасибо.
Ответ №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 более высокого порядка полно такого рода программ.
Комментарии:
1. Большое вам спасибо. Я думаю, что прочитаю эту книгу. Звучит забавно!
Ответ №3:
Вам нужен amp;
прототип.
sub foo(amp;@) {
my ($callback) = shift;
...
$callback->(...);
...
}
создает
foo { ... } ...;
эквивалентно
foo(sub { ... }, ...);
Ответ №4:
Хотя другие уже ответили на вопрос, мне все еще не хватало ссылки на официальную документацию Perl.