如何有条件地定义Perl子例程?

时间:2009-02-25 23:11:19

标签: perl function

我想定义一个依赖于命令行参数的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 个答案:

答案 0 :(得分:24)

其他人已经提供了您请求的语法,但我建议为此使用更明确的子例程引用,以便您可以自由地操作引用而无需操作定义。例如:

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

这是相同的基本方法,但我认为这种语法可以让您在添加更多每个参数时更方便地将参数映射到子例程。请注意,这是Strategy Pattern的变体,如果您涉及到这种情况。

答案 1 :(得分:16)

你想做的事情可以这样实现:

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

答案 2 :(得分:11)

我个人并没有做过很多这样的事情,但你可能想用一个变量来保存子程序:

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

或者,正如Leon Timmermans所建议的那样:

$difference->(args);

一些解释 - 这声明了一个名为$difference的变量,并根据您的条件将其设置为将引用保存到匿名子例程。所以你必须取消引用 $difference作为子程序(因此前面是&)才能调用子程序。

编辑:代码测试并运作。

另外一个编辑:

耶稣,我已经习惯use strictwarnings,我忘了他们是可选的。

但是说真的。始终use strict;use warnings;。这将有助于捕捉这样的事情,并给你很好的有用的错误信息,解释什么是错的。由于strictwarnings,我从未在生活中使用调试器 - 这就是错误检查消息的好坏。他们会捕捉到这样的各种事情,甚至会为你提供有用的信息为什么他们错了。

所以,无论何时你写一些东西,无论多小(除非它被混淆),总是use strict;use warnings;

答案 3 :(得分:6)

Subs在编译时定义 - >如果您启用了“使用警告”,则会看到有关子例程重新定义的错误消息。

答案 4 :(得分:1)

其他答案是正确的,使用代码引用或别名。但是别名示例引入了yicky typeglob语法,忘了处理strict。

Alias是一个经常被遗忘的模块,它包含了在保持严格的同时为引用提供名称所需的所有魔法。

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

你有问题。

别名对于在运行时使用闭包生成类似函数最有用,而不是使用cut&amp ;;手工粘贴它们。但这是另一个问题。

答案 5 :(得分:0)

感谢您提供有关如何使代码正常工作的所有建议。为了完整起见,我会给出问题的高级答案。

  1. 第一个构造不起作用,因为函数是在编译时定义的,但条件和/或命令行参数是在运行时计算的。到评估条件时,已经定义了命名函数。

  2. 编译器确实发出了“使用警告”的警告,虽然不是对于不知道1的程序员非常有用的警告:-)给出有意义的警告的困难是在if语句中定义函数可以使感觉如果你也对if语句中的函数做了些什么,就像Leon Timmermans的建议那样。原始代码编译为空的if语句,并且编译器未设置为警告这些。

  3. 严格地说,不可能有条件地定义函数,但可以有条件地为函数定义引用(rbright)或别名(Leon Timmermans)。共识似乎是引用比别名更好,但我不太清楚为什么。

  4. 注意1:在您遇到这样的问题之前,评估顺序并不明显;人们可以设想一个Perl,它可以在编译时评估条件,只要它可以安全地完成。显然Perl没有这样做,因为下面的代码也给出了一个重新定义的子例程的警告。

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

答案 6 :(得分:-2)

另一种方式:

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