在Perl中,确定coderef包的最可靠方法是什么?

Eri*_*rom 10 perl higher-order-functions

我有许多高阶实用程序函数,它们接受代码引用并将该代码应用于某些数据.其中一些函数需要在执行子例程期间本地化变量.开始时,我正在使用caller以与此示例reduce函数中所示类似的方式确定要本地化的包:

sub reduce (&@) {
    my $code      = shift;
    my $caller    = caller;
    my ($ca, $cb) = do {
        no strict 'refs';
        map \*{$caller.'::'.$_} => qw(a b)
    };
    local (*a, *b) = local (*$ca, *$cb);
    $a = shift;
    while (@_) {
        $b = shift;
        $a = $code->()
    }
    $a
}
Run Code Online (Sandbox Code Playgroud)

最初这种技术运行良好,但是当我尝试围绕高阶函数编写包装函数时,找出正确的调用者变得复杂.

sub reduce_ref (&$) {&reduce($_[0], @{$_[1]})}
Run Code Online (Sandbox Code Playgroud)

现在为了reduce工作,我需要这样的东西:

    my ($ca, $cb) = do {
        my $caller = 0;
        $caller++ while caller($caller) =~ /^This::Package/;
        no strict 'refs';
        map \*{caller($caller).'::'.$_} => qw(a b)
    };
Run Code Online (Sandbox Code Playgroud)

在这一点上,它成了一个问题,即要跳过哪些包,并结合从不使用这些包中的函数的规则.必须有一个更好的方法.

事实证明,高阶函数作为参数的子例程包含足够的元数据来解决问题.我目前的解决方案是使用B内省模块来确定传​​入的子程序的编译存储.这样,无论在编译代码和执行代码之间发生了什么,高阶函数总是知道正确的本地化包.

    my ($ca, $cb) = do {
        require B;
        my $caller = B::svref_2object($code)->STASH->NAME;
        no strict 'refs';
        map \*{$caller.'::'.$_} => qw(a b)
    };
Run Code Online (Sandbox Code Playgroud)

所以我的最终问题是,在这种情况下,这是否是确定来电者套餐的最佳方式?还有其他一些我没有想过的方法吗?我目前的解决方案是否还有一些错误?

ike*_*ami 5

首先,您可以使用以下内容而不需要任何更改:

sub reduce_ref (&$) { @_ = ( $_[0], @{$_[1]} ); goto &reduce; }
Run Code Online (Sandbox Code Playgroud)

但总的来说,以下确实是你想要的:

B::svref_2object($code)->STASH->NAME
Run Code Online (Sandbox Code Playgroud)

你想要sub的变量$a$b变量__PACKAGE__,所以你想知道sub的__PACKAGE__,这正是它返回的东西.它甚至修复了以下内容:

{
   package Utils;
   sub mk_some_reducer {
      ...
      return sub { ... $a ... $b ... };
   }
}

reduce(mk_some_reducer(...), ...)
Run Code Online (Sandbox Code Playgroud)

它并不能解决所有问题,但如果不使用参数而不是$a和,这是不可能的$b.