Как я могу условно определить подпрограмму Perl?
Я хочу определить функцию Perl (назовем ее «разница»), которая зависит от аргумента командной строки. Следующий код не работает:
if ("square" eq $ARGV[0]) {sub difference {return ($_[0] - $_[1]) ** 2}}
elsif ("constant" eq $ARGV[0]) {sub difference {return 1}}
Похоже, что условие игнорируется, и поэтому функция «разница» получает второе определение независимо от значения $ ARGV [0].
Я могу заставить код работать, поставив в функцию условие:
sub difference {
if ("square" eq $ARGV[0]) {return ($_[0] - $_[1]) ** 2}
elsif ("constant" eq $ARGV[0]) {return 1}
}
Но это не совсем мое намерение - мне не нужно, чтобы условие оценивалось каждый раз во время выполнения. Мне просто нужен способ повлиять на определение функции.
Мои вопросы:
- Почему не работает первая конструкция?
- Почему не выдает ошибку или другое указание на то, что что-то не так?
- Есть ли способ условно определять функции в Perl?
Ответов (7)7
Другие уже представили запрошенный синтаксис, но я бы рекомендовал использовать для этого более явные ссылки на подпрограммы, чтобы вы могли свободно манипулировать ссылкой, не изменяя определение. Например:
sub square_difference { return ($_[0] - $_[1]) ** 2 }
sub constant_difference { return 1 }
my %lookup = (
'square' => \&square_difference,
'constant' => \&constant_difference,
);
my $difference = $lookup{$ARGV[0]} || die "USAGE: $0 square|constant\n";
print &$difference(4, 1), "\n";
Это тот же базовый подход, но я думаю, что этот синтаксис позволит вам более удобно отображать аргументы в подпрограммы по мере добавления каждой из них. Обратите внимание, что это разновидность паттерна стратегии , если вам нравятся подобные вещи.
Я лично не делал этого много, но вы можете использовать переменную для хранения подпрограммы:
my $difference;
if ("square" eq $ARGV[0]) {$difference = sub {return ($_[0] - $_[1]) ** 2}}
elsif ("constant" eq $ARGV[0]) {$difference = sub {return 1}}
Звоните с:
&{ $difference }(args);
Или:
&$difference(args);
Или, как предположил Леон Тиммерманс:
$difference->(args);
Небольшое пояснение - здесь объявляется переменная с именем $difference
и, в зависимости от ваших условий, устанавливается, чтобы она содержала ссылку на анонимную подпрограмму. Таким образом, вы должны разыменовать $difference
подпрограмму (отсюда и &
впереди), чтобы она вызывала подпрограмму.
РЕДАКТИРОВАТЬ: код протестирован и работает.
Еще одно РЕДАКТИРОВАНИЕ:
Иисус, я так привык к use
ING strict
и warnings
что я забыл , они необязательны.
Но серьезно. Всегда use strict;
и use warnings;
. Это поможет уловить подобные вещи и даст вам полезные полезные сообщения об ошибках, объясняющие, в чем проблема. Я никогда не приходилось использовать отладчик в моей жизни из - за strict
и warnings
- вот как хорошо проверки ошибок сообщения являются. Они поймают всевозможные подобные вещи и даже дадут вам полезные сообщения о том, почему они ошибаются.
Поэтому, пожалуйста, всякий раз, когда вы пишете что-то, независимо от того, насколько он маленький (если он не запутан), всегда use strict;
и use warnings;
.
Спасибо за все предложения о том, как заставить код работать. Для полноты картины дам высокоуровневые ответы на свой вопрос.
Первая конструкция не работает, потому что функции определяются во время компиляции, но условия и / или аргументы командной строки оцениваются во время выполнения. К моменту оценки условия названная функция уже определена.
Компилятор выдает предупреждение с помощью «предупреждений использования», хотя это не очень полезно для программиста, не знающего 1 :-) Трудность с выдачей значимого предупреждения заключается в том, что определение функций внутри оператора if может иметь смысл, если вы также что-то делаете с функцией внутри оператора if, как в предложении Леона Тиммерманса. Исходный код компилируется в пустой оператор if, и компилятор не настроен предупреждать об этом.
Строго говоря, невозможно определить функции условно, но можно условно определить ссылки (rbright) или псевдонимы (Леон Тиммерманс) на функции. Похоже, что все согласны с тем, что ссылки лучше, чем псевдонимы, хотя я не совсем уверен, почему.
Обратите внимание на 1: порядок оценки не очевиден, пока вы не столкнетесь с такой проблемой; можно представить себе Perl, который будет оценивать условия во время компиляции всякий раз, когда это можно сделать безопасно. Очевидно, Perl этого не делает, поскольку следующий код также выдает предупреждение о переопределенной подпрограмме.
use warnings ;
if (1) {sub jack {}} else {sub jack {}}
Другие ответы верны, если используется ссылка на код или псевдоним. Но примеры псевдонимов вводят неприятный синтаксис typeglob и забывают иметь дело со строгим.
Псевдоним - это часто забываемый модуль, который включает в себя всю магию, необходимую для присвоения ссылке имени, при этом соблюдая строгость.
use strict;
use Alias;
my $difference_method = $ARGV[0];
if( "square" eq $difference_method ) {
alias difference => sub { return ($_[0] - $_[1]) ** 2 };
}
elsif( "constant" eq $difference_method ) {
alias difference => sub { return 1 };
}
else {
die "Unknown difference method $difference_method";
}
А теперь difference($a, $b)
работает.
Если вам нужно вызвать только difference()
свой собственный код, т.е. вы не собираетесь экспортировать его как функцию, я бы просто использовал ссылку на код и забыл о псевдониме.
my $difference_method = $ARGV[0];
my $Difference;
if( "square" eq $difference_method ) {
$Difference => sub { return ($_[0] - $_[1]) ** 2 };
}
elsif( "constant" eq $difference_method ) {
$Difference => sub { return 1 };
}
else {
die "Unknown difference method $difference_method";
}
$Difference->($a, $b);
Условное изменение того, что делает функция, усложняет отслеживание кода и делает его менее гибким, как и изменение поведения в любом глобальном. Это становится более очевидным, когда вы понимаете, что просто оптимизируете это:
my $Difference_Method = $ARGV[0];
sub difference {
if( $Difference_Method eq 'square' ) {
return ($_[0] - $_[1]) ** 2;
}
elsif( $Difference_Method eq 'constant' ) {
return 1;
}
else {
die "Unknown difference method $Difference_Method";
}
}
Каждый раз, когда у вас есть подпрограмма формы ...
sub foo {
if( $Global ) {
...do this...
}
else {
...do that...
}
}
У тебя проблемы.
Псевдонимы наиболее полезны для создания похожих функций во время выполнения с использованием замыканий, а не вырезания и вставки их вручную. Но это уже другой вопрос.