Как я могу условно определить подпрограмму 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}
}

Но это не совсем мое намерение - мне не нужно, чтобы условие оценивалось каждый раз во время выполнения. Мне просто нужен способ повлиять на определение функции.

Мои вопросы:

  1. Почему не работает первая конструкция?
  2. Почему не выдает ошибку или другое указание на то, что что-то не так?
  3. Есть ли способ условно определять функции в Perl?

Ответов (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";

Это тот же базовый подход, но я думаю, что этот синтаксис позволит вам более удобно отображать аргументы в подпрограммы по мере добавления каждой из них. Обратите внимание, что это разновидность паттерна стратегии , если вам нравятся подобные вещи.

Подпрограммы определяются во время компиляции -> если бы у вас были включены «предупреждения об использовании», вы бы увидели сообщение об ошибке о переопределении подпрограммы.

То, что вы хотите сделать, может быть достигнуто следующим образом:

if ($ARGV[0] eq 'square') {
    *difference = sub { return ($_[0] - $_[1]) ** 2 };
}
elsif ($ARGV[0] eq 'constant') {
    *difference = sub { return 1 };
}

Я лично не делал этого много, но вы можете использовать переменную для хранения подпрограммы:

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

Еще один способ:

my $diffmode;
BEGIN { $diffmode = $ARGV[0] }
sub difference {
    if ($diffmode eq 'square') { ($_[0] - $_[1]) ** 2 }
    elsif ($diffmode eq 'constant')  { 1 }
    else { "It don't make no never mind" }
}

Спасибо за все предложения о том, как заставить код работать. Для полноты картины дам высокоуровневые ответы на свой вопрос.

  1. Первая конструкция не работает, потому что функции определяются во время компиляции, но условия и / или аргументы командной строки оцениваются во время выполнения. К моменту оценки условия названная функция уже определена.

  2. Компилятор выдает предупреждение с помощью «предупреждений использования», хотя это не очень полезно для программиста, не знающего 1 :-) Трудность с выдачей значимого предупреждения заключается в том, что определение функций внутри оператора if может иметь смысл, если вы также что-то делаете с функцией внутри оператора if, как в предложении Леона Тиммерманса. Исходный код компилируется в пустой оператор if, и компилятор не настроен предупреждать об этом.

  3. Строго говоря, невозможно определить функции условно, но можно условно определить ссылки (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...
    }
}

У тебя проблемы.

Псевдонимы наиболее полезны для создания похожих функций во время выполнения с использованием замыканий, а не вырезания и вставки их вручную. Но это уже другой вопрос.