Как я могу условно определить подпрограмму 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:  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 как подпрограмма (отсюда & in front) для того, чтобы вызвать подпрограмму.

EDIT: код протестирован и работает.

еще одно редактирование:

Господи, я так привык к useing strict и warnings что я забыл, что они необязательны.

но серьезно. Всегда use strict; и use warnings;. Это поможет поймать такие вещи, и дать вам хорошие полезные сообщения об ошибках, которые объясняют, что не так. Мне никогда не приходилось использовать отладчик в моей жизнь из-за strict и warnings - вот насколько хороши сообщения об ошибках. Они будут ловить все виды вещей, как это, и даже дать вам полезные сообщения о почему они ошибаются.

поэтому, пожалуйста, всякий раз, когда вы пишете что-то, независимо от того, насколько мало (если это не запутано), всегда use strict; и use warnings;.


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


другие ответы верны, используя либо ссылку на код, либо псевдоним. Но примеры псевдонимов вводят синтаксис yicky 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...
    }
}

у вас проблема.

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


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

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

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

  3. строго говоря, условно определить невозможно функции, но можно условно определить ссылки (rbright) или псевдонимы (Leon Timmermans) на функции. По общему мнению, ссылки лучше, чем псевдонимы, хотя я не совсем уверен, почему.

примечание о 1: порядок оценки не очевиден, пока вы не столкнетесь с такой проблемой; можно было бы представить Perl, который будет оценивать условия во время компиляции, когда это можно было бы сделать безопасно. По-видимому, Perl этого не делает, так как следующий код также дает предупреждение о переопределенной подпрограмме.

use warnings ;
if (1) {sub jack {}} else {sub jack {}}

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

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" }
}