简体   繁体   English

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

[英]How can I conditionally define a Perl subroutine?

I want to define a Perl function (call it "difference") which depends on a command-line argument.我想定义一个取决于命令行参数的 Perl function (称之为“差异”)。 The following code doesn't work:以下代码不起作用:

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

It appears that the condition is ignored, and therefore the "difference" function gets the second definition regardless of the value of $ARGV[0].似乎忽略了条件,因此无论 $ARGV[0] 的值如何,“差异”function 都会获得第二个定义。

I can make the code work by putting a condition into the function:我可以通过将条件放入 function 来使代码工作:

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

But this is not really my intention -- I don't need the condition to be evaluated each time during execution.但这并不是我的真正意图——我不需要在执行期间每次都评估条件。 I just need a way to influence the definition of the function.我只需要一种方法来影响 function 的定义。

My questions are:我的问题是:

  1. Why does the first construction not work?为什么第一次施工不起作用?
  2. Why does it not give an error, or some other indication that something is wrong?为什么它没有给出错误或其他一些表明有问题的迹象?
  3. Is there a way to conditionally define functions in Perl?有没有办法有条件地定义 Perl 中的函数?

Others have already presented the syntax you requested, but I would recommend using more explicit subroutine references for this, so that you can freely manipulate the reference without manipulating the definition.其他人已经提供了您要求的语法,但我建议为此使用更明确的子例程引用,以便您可以自由地操作引用而无需操作定义。 For example:例如:

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

It's the same basic approach, but I think this syntax will let you map arguments to subroutines a bit more conveniently as you add more of each.这是相同的基本方法,但我认为这种语法将让您 map arguments 更方便地添加到每个子程序中。 Note this is a variation on the Strategy Pattern , if you're into that kind of thing.请注意,这是Strategy Pattern的一个变体,如果你喜欢这种东西的话。

What you want to do can be achieved like this:你想要做的可以这样实现:

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

I haven't personally done a lot of this, but you might want to use a variable to hold the subroutine:我个人并没有做过很多这样的事情,但是您可能想使用一个变量来保存子例程:

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

Call with:致电:

&{ $difference }(args);

Or:或者:

&$difference(args);

Or, as suggested by Leon Timmermans:或者,正如 Leon Timmermans 所建议的:

$difference->(args);

A bit of explanation - this declares a variable called $difference and, depending on your conditions, sets it to hold a reference to an anonymous subroutine.一点解释 - 这声明了一个名为$difference的变量,并根据您的条件将其设置为保存对匿名子例程的引用 So you have to dereference $difference as a subroutine (hence the & in front) in order for it to call the subroutine.因此,您必须取消引用$difference作为子例程(因此前面的& )才能调用子例程。

EDIT: Code tested and works.编辑:代码经过测试并且有效。

One more EDIT:再编辑:

Jesus, I'm so used to use ing strict and warnings that I forget they're optional.耶稣,我已经习惯了use strictwarnings ,以至于我忘记了它们是可选的。

But seriously.不过实话说。 Always use strict;始终use strict; and use warnings;use warnings; . . That will help catch things like this, and give you nice helpful error messages that explain what's wrong.这将有助于捕捉这样的事情,并为您提供有用的错误消息来解释问题所在。 I've never had to use a debugger in my life because of strict and warnings - that's how good the error-checking messages are.由于strictwarnings ,我在生活中从未使用过调试器——这就是错误检查消息的好处。 They'll catch all kinds of things like this, and even give you helpful messages as to why they're wrong.他们会捕捉到各种各样的事情,甚至会向您提供有用的信息,说明他们为什么错了。

So please, whenever you write something, no matter how small (unless it's obfuscated), always use strict;所以请,无论何时你写的东西,无论多么小(除非它被混淆),总是use strict; and use warnings;use warnings; . .

Subs are defined at compile time -> if you had "use warnings" enabled, you would have seen an error message about subroutine redefinition.子程序是在编译时定义的 -> 如果您启用了“使用警告”,您会看到有关子程序重新定义的错误消息。

Other answers are correct, using either a code reference or an alias.其他答案是正确的,使用代码参考或别名。 But the aliasing examples introduce the yicky typeglob syntax and forget to deal with strict.但是别名示例引入了 yicky typeglob 语法并且忘记了处理严格。

Alias is an oft forgotten module that wraps up all the magic needed to give a reference a name all while keeping 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";
}

And now difference($a, $b) works.现在difference($a, $b)起作用了。

If you only need to call difference() inside your own code, ie.如果您只需要在自己的代码中调用difference() ,即。 you're not going to export it as a function, I would just use a code reference and forget the aliasing.您不会将其导出为 function,我只会使用代码引用并忘记别名。

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

Conditionally changing what a function does makes the code harder to follow and less flexible, just like changing behavior on any global.有条件地更改 function 所做的事情会使代码更难遵循且灵活性降低,就像在任何全局上更改行为一样。 It becomes more obvious when you realize that you're just optimizing this:当您意识到您只是在优化它时,它变得更加明显:

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

Any time you have a subroutine of the form...任何时候你有一个形式的子程序......

sub foo {
    if( $Global ) {
        ...do this...
    }
    else {
        ...do that...
    }
}

You have a problem.你有问题。

Aliasing is most useful for generating similar functions at run time using closures, rather than cut & pasting them by hand.别名对于在运行时使用闭包生成类似函数最有用,而不是手动剪切和粘贴它们。 But that's for another question.但这是另一个问题。

Thanks for all the suggestions for how to make the code work.感谢所有关于如何使代码工作的建议。 Just for completeness, I'll give the high-level answers to my question.为了完整起见,我将给出我的问题的高级答案。

  1. The first construction doesn't work because functions are defined at compile time but conditions and/or command-line arguments are evaluated at runtime.第一个构造不起作用,因为函数是在编译时定义的,但条件和/或命令行 arguments 是在运行时评估的。 By the time the condition is evaluated, the named function has already been defined.在评估条件时,已定义名为 function 的名称。

  2. The compiler does give a warning with "use warnings", though not one that's very useful for a programmer unaware of 1:-) The difficulty with giving a meaningful warning is that defining functions inside an if statement can make sense if you also do something with the function within the if statement, as in Leon Timmermans's suggestion.编译器确实给出了一个带有“使用警告”的警告,尽管对于不知道 1 的程序员来说这不是非常有用的:-) 给出有意义的警告的困难在于,如果你也做某事,在 if 语句中定义函数是有意义的如 Leon Timmermans 的建议,在 if 语句中使用 function。 The original code compiles to a vacuous if statement, and the compiler is not set to warn about these.原始代码编译为一个空的 if 语句,并且编译器没有设置为对这些发出警告。

  3. Strictly speaking, it is not possible to conditionally define functions, but it is possible to conditionally define references (rbright) or aliases (Leon Timmermans) to functions.严格来说,不可能有条件地定义函数,但可以有条件地定义对函数的引用(rbright)或别名(Leon Timmermans)。 The consensus seems to be that references are better than aliases, though I'm not quite sure why.共识似乎是引用比别名更好,尽管我不太清楚为什么。

Note about 1: the evaluation order is not obvious until you've actually run into a problem like this;关于 1 的注意事项:在您实际遇到这样的问题之前,评估顺序并不明显; one could envision a Perl which would evaluate conditions at compile time whenever it could be done safely.可以设想一个 Perl,只要可以安全地完成,它就会在编译时评估条件。 Apparently Perl doesn't do this, since the following code too gives a warning about a redefined subroutine.显然 Perl 没有这样做,因为下面的代码也给出了关于重新定义的子程序的警告。

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

Yet another way:还有一种方式:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM