Perl 将标量/列表上下文传递给被调用的子例程

Perl 将标量/列表上下文传递给被调用的子例程,perl,Perl,我正在尝试编写一个接受coderef参数的sub。我的sub执行一些初始化,调用coderef,然后执行一些清理 我需要使用调用sub的相同上下文(标量、列表、void上下文)调用coderef。我能想到的唯一办法是这样: sub perform { my ($self, $code) = @_; # do some initialization... my @ret; my $ret; if (not defined wantarray) {

我正在尝试编写一个接受coderef参数的sub。我的sub执行一些初始化,调用coderef,然后执行一些清理

我需要使用调用sub的相同上下文(标量、列表、void上下文)调用coderef。我能想到的唯一办法是这样:

sub perform {
    my ($self, $code) = @_;

    # do some initialization...

    my @ret;
    my $ret;

    if (not defined wantarray) {
        $code->();
    } elsif (wantarray) {
        @ret = $code->();
    } else {
        $ret = $code->();
    }

    # do some cleanup...

    if (not defined wantarray) {
        return;
    } elsif (wantarray) {
        return @ret;
    } else {
        return $ret;
    }
}
sub perform {
    my ($self, $code) = @_;

    # do some initialization...

    my @ret;
    if (not defined wantarray) {
        $code->();
    } else {
        @ret = wantarray ? $code->() : scalar $code->();
    }

    # do some cleanup...

    return wantarray ? @ret : $ret[0];
}
显然,这段代码中有大量冗余。有没有办法减少或消除这种冗余

EDIT我后来意识到我需要在
eval
块中运行
$code->()
,这样即使代码死了,清理也会运行。添加eval支持,并结合user502515和cjm的建议,下面是我的想法

sub perform {
    my ($self, $code) = @_;

    # do some initialization...

    my $w = wantarray;
    return sub {
        my $error = $@;

        # do some cleanup...

        die $error if $error;   # propagate exception
        return $w ? @_ : $_[0];
    }->(eval { $w ? $code->() : scalar($code->()) });
}

这消除了冗余,但不幸的是现在控制流有点难以遵循。

您可以排除
!很早就定义了wantarray
案例,因为不需要进行清理(因为
$code->()
的结果(如果有的话)没有存储)。这将从剩余函数中删除一个案例,使其更简单

其次,您可以将清理内容移动到它自己的函数中。我想到了这样一件事:

sub perform { my($self, $code) = @_; if (!defined(wantarray)) { $code->(); return; } return wantarray ? &cleanup($code->()) : &cleanup(scalar($code->())); } 替补表演 { 我的($self,$code)=@; 如果(!已定义(wantarray)){ $code->(); 返回; } 返回wantarray?&cleanup($code->()):&cleanup(scalar($code->()); }
我想我会这样做:

sub perform {
    my ($self, $code) = @_;

    # do some initialization...

    my @ret;
    my $ret;

    if (not defined wantarray) {
        $code->();
    } elsif (wantarray) {
        @ret = $code->();
    } else {
        $ret = $code->();
    }

    # do some cleanup...

    if (not defined wantarray) {
        return;
    } elsif (wantarray) {
        return @ret;
    } else {
        return $ret;
    }
}
sub perform {
    my ($self, $code) = @_;

    # do some initialization...

    my @ret;
    if (not defined wantarray) {
        $code->();
    } else {
        @ret = wantarray ? $code->() : scalar $code->();
    }

    # do some cleanup...

    return wantarray ? @ret : $ret[0];
}

您仍然有两个
wantarray
检查,但是您的
cleanup
函数需要一个检查才能正确返回传入的值。在第二次检查中,您不必担心
unde
的情况,因为在这种情况下,
perform
返回什么并不重要。

在CPAN上检查模块。我认为它可以让你做你想做的事情(可能还有更多)。

我还应该指出,如果
不需要做一些清理…
,整个函数可以简化为
return$code->()
,上下文将自动被调用的子例程继承。但不幸的是,清理是必要的。“执行”吗?这是马戏团的代码吗?不幸的是,即使从void上下文调用,清理也需要始终进行。但是我喜欢你的建议,即使用一个单独的
清理
子例程来自动为被调用的子例程提供上下文。尽管这是一个常见的奇怪的Damianized接口,但它确实解决了这方面的问题,即使它有些过分。我不知道Context::Return如何添加除了另一个依赖项之外的任何东西。这真的不是它试图解决的问题。它不能帮助您将正确的上下文传递给coderef,而coderef实际上是这个问题中较难的一半。和
返回wantarray@ret:$ret[0]
不够复杂,不足以保证加载一个模块来“简化”它。@cjm:谢谢您的编辑……CPAN页面何时才能提供指向任何模块的通用链接?这是一个令人讨厌的问题——我还没有记住在没有版本的情况下访问特定模块的“正确”方法,而遵循您的链接最终会得到我提供的链接(今天;我知道它随时都可能改变)。如果这意味着有一个规范的链接,这将是有益的,如果该网站会给一种方式来获得它!如果您查看CPAN页面的右上角(作者图片的正上方),您将看到单词“permalink”。这就是你想要的链接。