2011-07-12 51 views
10

我有許多更高級的實用程序函數,它們接受代碼引用並將該代碼應用於某些數據。其中一些功能需要在執行子程序期間對變量進行本地化。在開始的時候,我用caller來確定要定位哪個包入,以類似的方式,如本示例中所示reduce功能:但是,一旦在Perl中,確定coderef包的最可靠方法是什麼?

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) 
    }; 

在這一點上它成爲了一個問題,其中包跳過與從不使用該函數的紀律相結合從這些包中。必須有更好的方法。

事實證明,高階函數作爲參數的子例程包含足夠的元數據來解決問題。我當前的解決方案是使用B自檢模塊來確定傳​​入子例程的編譯隱藏。這樣,無論在編譯代碼和執行代碼之間發生了什麼,高階函數總是知道要本地化的正確包。

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

所以我最終的問題是,如果這是在這種情況下確定來電者套餐的最佳方式?有沒有其他的方式,我沒有想到?當前的解決方案是否有一些等待發生的錯誤?

+2

這似乎是非常依賴於實現的依賴性......你對於在未來的Perl版本中沒有改變這一點有多自信?使用對象代替原始函數會不會更簡單,更健壯?是否讓每個對象都存儲一個函數並記住相應的包? – Nemo

回答

5

首先,你可以使用以下命令,不需要任何改變:

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

但一般來說,以下是你確實想要什麼:

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

你想要$a$b子分組__PACKAGE__的變量,所以你想知道子分組的__PACKAGE__,這正是返回的結果。它甚至還修復了以下情況:

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

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

它不會解決所有問題,但沒有使用參數,而不是$a$b這是不可能的。

+0

我知道有人會提到'goto&sub'解決方法:)這是我通常的解決方案,但在這種情況下,真正的包裝需要本地化其他變量,或者需要後處理HOF的結果。關於Nemo關於' - > STASH-> NAME'接口穩定性的評論,你認爲假設B接口不會改變是安全的嗎? –

1

如果有人需要他們,這裏是我最終決定使用的功能:這將是作爲

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)); 
在原來的問題的情況下

相關問題