在Perl中正确转发返回值

时间:2014-12-14 20:20:10

标签: perl return-type

我对Perl很陌生,很难将我的脑袋缠绕在非常隐含的类型系统上。我想要实现的是一个简单的包装函数,它与它包装的函数具有相同的签名,因此可以在其中使用它。

所以,假设我有一个我想要包装的现有函数orig。我接受了许多输入参数,并根据这些参数具有不同的返回类型。只要我按以下方式编写包装器,返回类型与原始函数相同,并且一切都很好:

sub wrapper {
    my ($first) = @_;
    print "before. first argument: $first\n";
    return orig(@_);
}

但是,如果我想在执行orig后在包装器中执行一些代码,我不知道如何保留类型。根据我的理解,对perl函数的输入始终是一个标量数组,输出也是如此。所以解决方案应该是这样的:

sub wrapper {
    my ($first) = @_;
    print "before. first argument: $first\n";
    my @result = orig(@_);
    print "after";
    return @result;
}

但这似乎并没有像预期的那样奏效。我错过了什么?如何编写这样的包装函数,使其适用于任意返回类型?

2 个答案:

答案 0 :(得分:7)

  

从我的理解,与perl函数的输入相同   总是一组标量,输出也是如此。

不,不完全。

可以在列表上下文,标量上下文或void上下文中调用Perl函数。

some_function(@args);                  # void
my $result  = some_function(@args);    # scalar
my @results = some_function(@args);    # list

Perl的许多内置函数根据调用它们的上下文采取不同的行为。例如,grep返回列表上下文中的结果列表,以及标量上下文中的结果计数。

如果您正在编写自己的函数并希望在不同的上下文中表现不同,则该函数可以使用wantarray关键字来检测它被调用的上下文。wantarray返回true对于列表上下文,对于标量上下文为false,对于void上下文为undef。

即使你不是有意识地想要根据上下文编写一个行为不同的函数,你最终也可能通过返回一个上下文相关的表达式(例如grepmap)而意外地这样做,或数组(标量上下文中的数组返回其长度)。

在不破坏上下文的情况下包装函数的正确方法是这样的。是的,我意识到它很不错。

sub wrapper {
   my ($first) = @_;
   print "before. first argument: $first\n";
   my @result = 
      wantarray         ? orig(@_) :
      defined wantarray ? scalar orig(@_) :
      do { orig(@_); () };
   print "after";
   wantarray ? @result : $result[0];
}

现在,如果您的包装器不需要更改@_,并且不需要更改返回值,那么Class::Method::Modifiers可以使这更容易:

use Class::Method::Modifiers;

sub wrapper { orig(@_) }  # do nothing in the wrapper itself

before wrapper => sub {
   my ($first) = @_;
   print "before. first argument: $first\n";
};

after wrapper => sub {
   print "after";
};

答案 1 :(得分:1)

我怀疑在Perl中有更好的方法可以做你想要的,但是因为你只指定了机制,我只能告诉你如何让它工作。

Perl子例程的返回值取决于调用的 context 。在子例程中,您可以使用内置运算符wantarray来检测上下文 - 如果调用位于列表上下文中,它将返回 true 值,并且 false 否则。

因此,要传递另一个子例程在同一个上下文中返回的值,您必须像这样写wrapper

sub wrapper {
    my ($first) = @_;

    print "before. first argument: $first\n";

    if (wantarray) {
      my @result = orig(@_);
      print "after";
      return @result;
    }
    else {
      my $result = orig(@_);
      print "after";
      return $result;
    }
}

但请记住,给定的子例程可能是在 标量列表上下文中调用的。根据上下文编写行为不同的内容很少,因此通常只需要这两个条件分支中的一个,具体取决于orig的行为。