2009-08-24 112 views
7

所以我有一個Perl類。它有一個sort()方法,我希望它是或多或少相同的內置sort()功能:Perl的變量範圍問題

$object->sort(sub ($$) { $_[0] <=> $_[1] }); 

但我不能這樣做:

$object->sort(sub { $a <=> $b }); 

因爲作用域。但是List :: Util模塊用reduce()來做到這一點。我看了看錶::的Util模塊,和他們做一些比較討厭的事情no strict 'vars'做到這一點。我試過了,但無濟於事。

我的理解是,reduce()的工作方式是因爲它被導出到適當的名稱空間,因此我的類無法做到這一點,因爲函數在另一個名稱空間中非常穩固。這是正確的嗎?或者在我的情況下是否有一些(無疑是更可怕和不明智的)方法來做到這一點?

回答

8

好了,其他兩個答案都對了一半。這裏是一個真正能對一個有效的解決方案:

package Foo; 

use strict; 
use warnings; 

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

    my ($pkg) = caller; 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(sub { $a <=> $b }); 
# 1 6 39 2 5 
# 1 2 5 6 39 

想必你會整理一些數據,實際上是對象的一部分。

你需要caller魔法,所以你需要在調用者的包中定位$a$b,這是Perl要查找它們的地方。它創建的全局變量只在該子被調用時才存在。

請注意,您將獲得與warnings一個「只用過一次的名字」;不管怎樣,我確信你可以跳過一些箍來避免這種情況。

+2

這可能足以滿足您的需要,但它很脆弱。不能保證比較函數與'sort'方法的調用者屬於同一個包。這就是Sub :: Identify進來的地方。 – cjm 2009-08-25 05:29:58

+0

@cjm - 這是真的,我一定會看到Sub :: Identify,但是我的更大的問題是讓它工作,而不是在一般情況下工作。具體的解決方案比一般的失敗要好。但是,將這個答案與你的結合起來會給我一個通用的解決方案,這是一件好事。 – 2009-08-25 05:37:18

+1

雖然事實證明'sort'內建有同樣的問題。它假定比較函數來自與調用者相同的包。所以,如果你能忍受這一點,你可以保存對Sub :: Identify的依賴。 (或者你可以有條件地要求Sub :: Identify,如果沒有安裝,可以回到'caller',但這是更多的工作。) – cjm 2009-08-25 05:51:08

1

可以使用the local operator$a$b設定值的子程序調用的持續時間:

sub sample 
{ 
    my $callback = shift; 
    for (my $i = 0; $i < @_; $i += 2) { 
     local ($a, $b) = @_[$i, $i + 1]; 
     $callback->(); 
    } 
}  

sample sub { print "$a $b\n" }, qw(a b c d e f g h i j); 

如果你有一個普通的子程序,而不是方法,那麼你可以把它更加像sort,所以你不需要回調函數之前,使用sub。在功能使用的原型:

sub sample (&@) 

然後調用這樣稱呼它:

sample { print "$a $b\n" } qw(a b c d e f g h i j); 

方法,雖然不受原型的影響。

+0

他專門詢問了'method',而不是一個簡單的子。 – 2009-08-25 00:44:51

+3

如果你從課堂外調用該方法,那麼這將不起作用。您正在本地化*類的* $ a和$ b,而不是調用者的。 – cjm 2009-08-25 00:50:21

+0

啊。我以爲我看到了這個,但我猜不是。來自perlvar的我的印象是'$ a'和'$ b'足夠神奇,它們在這種情況下「正常工作」。 – 2009-08-25 01:06:49

3

你可以使用Sub::Identify找出與CODEREF相關的包(它被稱爲stash_name)。然後根據需要在該包中設置$ a和$ b。您可能需要在您的方法中使用no strict 'refs'才能使其工作。

這裏的EVEE的回答修改在一般情況下工作:

use strict; 
use warnings; 

package Foo; 

use Sub::Identify 'stash_name'; 

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

    my $pkg = stash_name($sub); 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package Sorter; 

sub compare { $a <=> $b } 

package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(\&Sorter::compare); 

$foo->sort(sub { $b <=> $a }); 
+1

List :: Util只使用'caller'。我認爲這是不夠的,在一般情況下,是嗎?如果調用者從其他包傳遞函數,那麼List :: Util將設置調用者的$ a而不是函數。 – 2009-08-25 01:09:45

+0

5.10.1中的List :: Util版本使用基本上做同樣的事情的XS代碼Sub :: Identify確實可以找出coderef所屬的包。 – cjm 2009-08-25 05:32:47

+0

或者,我可以在XS中重寫我的模塊,從而失去依賴性並給我一個學習XS的機會。但是,在此期間,我會研究這一點。 – 2009-08-25 05:47:04