В 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));
в контексте исходного вопроса.