В Perl, какой самый надежный способ определить пакет coderef?

У меня есть несколько функций полезности более высокого порядка, которые берут ссылку на код и применяют этот код к некоторым данным. Некоторые из этих функций требуют локализации переменных во время выполнения подпрограмм. В начале я использовал 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
}

Первоначально эта техника работала нормально, однако, как только я попытался написать функцию обертки вокруг функции более высокого порядка, выяснение того, что правильный вызывающий объект становится сложным.

sub reduce_ref (&$) {&reduce($_[0], @{$_[1]})}

Теперь, чтобы работать reduce, мне понадобится что-то вроде:

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

В этот момент встал вопрос о том, какие пакеты пропустить, в сочетании с дисциплиной, никогда не использующей функцию из этих пакетов. Должен быть лучший способ.

Оказывается, что подпрограмма, которую выполняют функции более высокого порядка в качестве аргумента, содержит достаточно метаданных для решения проблемы. Мое текущее решение использует модуль проверки int B, чтобы определить компиляцию stash переданной в подпрограмме. Таким образом, неважно, что происходит между компиляцией кода и его исполнением, функция более высокого порядка всегда знает правильный пакет для локализации.

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

Итак, мой последний вопрос в том, что это лучший способ определить пакет вызывающего абонента в этой ситуации? Есть ли другой способ, о котором я не думал? Есть ли ошибка с моим текущим решением?

Ответ 1

Сначала вы можете использовать следующие и не нуждаться в каких-либо изменениях:

sub reduce_ref (&$) { @_ = ( $_[0], @{$_[1]} ); goto &reduce; }

Но, вообще говоря, следующее именно то, что вы хотите:

B::svref_2object($code)->STASH->NAME

Вам нужны переменные $a и $b для sub __PACKAGE__, поэтому вы хотите знать sub __PACKAGE__, и это именно то, что возвращается. Он даже исправляет следующее:

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

reduce(mk_some_reducer(...), ...)

Он не исправляет все, но это невозможно без использования аргументов вместо $a и $b.

Ответ 2

В случае, если кто-то нуждается в них, вот функции, которые я в конечном итоге решил использовать:

require B;
use Scalar::Util 'reftype';
use Carp 'croak';

my $cv_caller = sub {
    reftype($_[0]) eq 'CODE' or croak "not code: $_[0]";
    B::svref_2object($_[0])->STASH->NAME
};

my $cv_local = sub {
    my $caller = shift->$cv_caller;
    no strict 'refs';
    my @ret = map \*{$caller.'::'.$_} => @_;
    wantarray ? @ret : pop @ret
};

который будет использоваться как:

my ($ca, $cb) = $code->$cv_local(qw(a b));

в контексте исходного вопроса.