2009-08-24 113 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