【问题标题】:How can a Perl force its caller to return? [duplicate]Perl 如何强制它的调用者返回? [复制]
【发布时间】:2010-05-22 18:15:30
【问题描述】:

可能重复:
Is it possible for a Perl subroutine to force its caller to return?

我想编写一个子程序,使调用者在某些条件下返回。这旨在用作验证函数输入的快捷方式。到目前为止我所拥有的是:

sub needs($$) {
    my ($condition, $message) = @_;

    if (not $condition) {
        print "$message\n";
        # would like to return from the *parent* here
    }

    return $condition;
}

sub run_find {
    my $arg = shift @_;
    needs $arg, "arg required" or return;
    needs exists $lang{$arg}, "No such language: $arg" or return;

    # etc.
}

needs 中的调用者返回的好处是避免在run_find 和类似函数中编写重复的or return

【问题讨论】:

  • 您确实意识到,正如您所写的那样,needs 将永远是return $condition,不是吗?

标签: perl scope return


【解决方案1】:

我认为您在这里专注于错误的事情。我用 Data::Constraint、Brick 等来做这类事情,并在 Mastering Perl 中讨论这个问题。只要稍微聪明一点,并考虑一下程序的结构和 Perl 所具有的动态特性,您就不需要这种严格的、程序化的方法。

但是,您需要弄清楚的第一件事是您在调用子例程中真正想知道的内容。如果您只想知道是或否,这很容易。

您的needs 的问题在于您正在考虑为每个条件调用一次,这迫使您使用needs 来控制程序流。那是错误的方法。 needs 只是为了给你一个答案。它的工作不是改变程序状态。如果您滥用它,它的用处就会大大降低,因为即使 needs 返回 false,其他一些调用子例程也可能想要继续。调用一次,让它返回一次。调用子例程使用返回值来决定它应该做什么。

基本结构涉及您传递给needs 的表。这是您的验证资料。

子 run_find { 我的 $arg = 班次 @_; 除非需要,否则返回 [ [ sub { $arg }, "需要 arg" ], [ sub { 存在 $lang{$arg} }, "没有这样的语言:$arg" ], ]; } ... }

您可以根据自己的要求构建表格。在needs 中,您只需处理表格:

sub needs($$) {
    my ($table) = @_;

    foreach $test ( @$table ) {
        my( $sub, $message ) = @$test;
        unless( $sub->(...) ) {
            print $message;
            return
            }
        }

    return 1;
    }

现在,这种方法真正酷的地方在于,您不必提前知道表格。您可以从配置或其他方法中提取它。这也意味着您可以动态更改表格。现在你的代码缩小了很多:

子 run_find { 我的 $arg = 班次 @_; 除非需要,否则返回($validators{run_find}); ... }

你可以继续这样做。在 Mastering Perl 中,我展示了几个解决方案,它们可以完全从代码中删除它并将其移动到配置文件中。也就是不改代码就可以改变业务规则。

请记住,几乎任何时候您输入相同的字符序列,您都可能做错了。 :)

【讨论】:

  • +1 不得不说桌子的创意真是太棒了。
  • 是的,餐桌创意满足了我的需求。它不需要任何古怪的作用域,但它仍然减少了冗长。
【解决方案2】:

听起来您正在重新发明异常处理。

needs 函数不应该神奇地推断出它的父级并中断父级的控制流——这是不礼貌的。如果在调用链中增加了额外的函数,需要回退两个甚至三个函数怎么办?您如何以编程方式确定这一点?调用者会期待他或她的函数早点返回吗?如果你想避免错误,你应该遵循最小意外原则——这意味着使用异常来表明存在问题,并让调用者决定如何处理它:

use Carp;
use Try::Tiny;

sub run_find {
    my $arg = shift;
    defined $arg or croak "arg required";
    exists $lang{$arg} or croak "no such language: $arg"; 

    ...
}

sub parent {
    try { run_find('foo') }
    catch { print $@; }
} 

try 块内的任何代码都是特殊的:如果有东西死了,异常会被捕获并存储在$@ 中。在这种情况下,catch 块被执行,它将错误打印到 STDOUT 并且控制流正常继续。

免责声明:Perl 中的异常处理很痛苦。我推荐 Try::Tiny,它可以防止许多常见的陷阱(并提供熟悉的 try/catch 语义)和 Exception::Class,以便快速创建异常对象,以便您区分 Perl 的错误和您自己的错误。

对于参数的验证,您可能会发现使用 CPAN 模块(例如 Params::Validate)更容易。

【讨论】:

  • 如果我写“catch { print $_; }”而不是“catch { print $@; }”,我只会收到错误消息
  • 问题是run_find 的调用者是我无法控制的模块(Term::Shell,如果你关心的话),并且调用者没有将我的调用包含在eval 中。既然不想拖垮整个流程,只好从run_find正常退出,换个方式处理错误。
【解决方案3】:

您可能想查看 kinopiko 最近提出的类似问题: Is it possible for a Perl subroutine to force its caller to return?

对此的执行摘要是:最好的解决方案是使用异常(die/eval、Try::Tiny 等...)。你也使用 GOTO 和可能的 Continuation::Escape

【讨论】:

    【解决方案4】:

    以这种方式做事没有意义;讽刺的是,你不需要needs

    这就是为什么。

    • run_find 写得不好。如果您的第一个条件为真,您将永远不会测试第二个条件,因为您已经拥有 returned。
    • warndie 函数无论如何都会为您提供打印和/或退出行为。

    如果您想在参数失败时终止执行,我会这样写您的run_find sub(将其重命名为well_defined):

    sub well_defined {
    
        my $arg = shift;
        $arg or die "arg required";
        exists $lang{$arg} or die "no such language: $arg";
        return 1;
    }
    

    应该有一种方法可以同时使用return 0warn,但我需要多尝试一下。

    run_find 也可以写入return 0 和适当的warn 消息,如果条件不满足,return 1 如果条件满足(重命名为well_defined)。

    sub well_defined {
    
        my $arg = shift;
        $arg or warn "arg required" and return 0;
        exists $lang{$arg} or warn "no such language: $arg" and return 0;
        return 1;
    }
    

    这会启用布尔型行为,如下所示:

    perform_calculation $arg if well_defined $arg; # executes only if well-defined
    

    【讨论】:

    • warn "no such language", return 0; 怎么样?
    • @Ether :在 Perl 单行代码中尝试了您的建议,但 warn 不正确:perl -e "sub rf { $arg = shift; $arg or warn qq(No arg), return 0; return 1; } print qq(Yes) if rf ;"。用and 替换, 确实有效。
    • 啊,是的,我忘记了 warn 需要一个列表,所以它会吞噬所有内容,除非我们添加一些括号来显示它的 args 结束位置:warn("no such language"), return 0;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-02
    • 1970-01-01
    • 2016-11-04
    • 1970-01-01
    相关资源
    最近更新 更多