Как я могу условно определить подпрограмму Perl? - PullRequest
9 голосов
/ 26 февраля 2009

Я хочу определить функцию 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 ]

24 голосов
/ 26 февраля 2009

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

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

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

16 голосов
/ 26 февраля 2009

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

if ($ARGV[0] eq 'square') {
    *difference = sub { return ($_[0] - $_[1]) ** 2 };
}
elsif ($ARGV[0] eq 'constant') {
    *difference = sub { return 1 };
}
11 голосов
/ 26 февраля 2009

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

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 strict и warnings, что я забыл, что они необязательны.

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

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

6 голосов
/ 26 февраля 2009

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

1 голос
/ 27 февраля 2009

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

У вас есть проблема.

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

0 голосов
/ 26 февраля 2009

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

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

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

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

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

use warnings ;
if (1) {sub jack {}} else {sub jack {}}
0 голосов
/ 26 февраля 2009

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

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" }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...