2010-09-30 94 views
11

假设我有一个实用程序库(other),其中包含我想用来返回任意排序数据的子程序 (sort_it)。 这可能比这更复杂,但是这说明了 关键概念:

#!/usr/local/bin/perl 

use strict; 

package other; 

sub sort_it { 
    my($data, $sort_function) = @_; 

    return([sort $sort_function @$data]); 
} 

现在让我们用它在另一个包。

package main; 
use Data::Dumper; 

my($data) = [ 
     {'animal' => 'bird',   'legs' => 2}, 
     {'animal' => 'black widow',  'legs' => 8}, 
     {'animal' => 'dog',    'legs' => 4}, 
     {'animal' => 'grasshopper',  'legs' => 6}, 
     {'animal' => 'human',   'legs' => 2}, 
     {'animal' => 'mosquito',  'legs' => 6}, 
     {'animal' => 'rhino',   'legs' => 4}, 
     {'animal' => 'tarantula',  'legs' => 8}, 
     {'animal' => 'tiger',   'legs' => 4}, 
     ], 

my($sort_by_legs_then_name) = sub { 
    return ($a->{'legs'} <=> $b->{'legs'} || 
      $a->{'animal'} cmp $b->{'animal'}); 
}; 

print Dumper(other::sort_it($data, $sort_by_legs_then_name)); 

这不起作用,由于一个微妙的问题。 $a$b是包 全局变量。当涉及 封闭时,他们指的是$main::a$main::b

我们可以说,而不是解决这个问题:

my($sort_by_legs_then_name) = sub { 
    return ($other::a->{'legs'} <=> $other::b->{'legs'} || 
      $other::a->{'animal'} cmp $other::b->{'animal'}); 
}; 

这工作,但迫使我们硬编码我们的应用程序包 的名字随处可见。如果要改变,我们需要记住更改 代码,而不仅仅是可能 在现实世界中的use other qw(sort_it);声明。

您可能会立即想到尝试使用__PACKAGE__。那风向 评估“主”。 eval("__PACKAGE__");也是如此。

有使用caller的作品一招:

my($sort_by_legs_then_name) = sub { 
    my($context) = [caller(0)]->[0]; 
    my($a) = eval("\$$context" . "::a"); 
    my($b) = eval("\$$context" . "::b"); 

    return ($a->{'legs'} <=> $b->{'legs'} || 
      $a->{'animal'} cmp $b->{'animal'}); 
}; 

但是,这是相当黑魔法。这似乎应该是 一些更好的解决方案。但是我还没有找到它,或者还没有算出它 。

+1

如果使用来电显示这样的,不会打破它一样多,如果所定义的子包,并调用其他:: sort_it包有什么不同? – aschepler 2010-09-30 01:31:10

回答

9

使用原型(Usenet posting,ysth最初提出的解决方案)。

适用于Perl> = 5.10.1(不确定较早)。

my($sort_by_legs_then_name) = sub ($$) { 
    my ($a1,$b1) = @_; 
    return ($a1->{'legs'} <=> $b1->{'legs'} || 
      $a1->{'animal'} cmp $b1->{'animal'}); 
}; 

我得到的结果:

$VAR1 = [ 
     { 
     'legs' => 2, 
     'animal' => 'bird' 
     }, 
     { 
     'legs' => 2, 
     'animal' => 'human' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'dog' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'rhino' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'tiger' 
     }, 
     { 
     'legs' => 6, 
     'animal' => 'grasshopper' 
     }, 
     { 
     'legs' => 6, 
     'animal' => 'mosquito' 
     }, 
     { 
     'legs' => 8, 
     'animal' => 'black widow' 
     }, 
     { 
     'legs' => 8, 
     'animal' => 'tarantula' 
     } 
    ]; 
+0

我不知道Perl6 ::占位符是否也能工作? (http://search.cpan.org/~lpalmer/Perl6-Placeholders-0.07/lib/Perl6/Placeholders.pm) – DVK 2010-09-30 01:47:35

+4

这个修改是在[Perl 5.6](http://search.cpan.org/~ gsar/Perl的5.6.0 /荚/ perldelta.pod#Enhanced_support_for_sort%28个%29_subroutines)。尽管如此,有[记录的性能损失](http://perldoc.perl.org/functions/sort.html)。 – 2010-09-30 02:30:22

+3

与使用匿名子例程相比,性能损失并没有那么糟糕,但两者都比使用块要慢很多:http://gist.github.com/603932这是一个抽象可能不是你的朋友的senario。 – 2010-09-30 02:44:34

0

这里是如何做到这一点:

sub sort_it { 
    my ($data, $sort) = @_; 
    my $caller = caller; 
    eval "package $caller;" # enter caller's package 
     . '[sort $sort @$data]' # sort at full speed 
     or die [email protected]    # rethrow any errors 
} 

eval这里需要因为package只需要裸包名称,而不是一个变量。

3

试试这个:

sub sort_it { 
    my($data, $sort_function) = @_; 
    my($context) = [caller(0)]->[0]; 
    no strict 'refs'; 
    local *a = "${context}::a"; 
    local *b = "${context}::b"; 
    return([sort $sort_function @$data]); 
} 

而且你不会在每次调用的开销买单。

但我宁愿

sub sort_it (&@) { 
    my $sort_function = shift; 
    my($context) = [caller(0)]->[0]; 
    no strict 'refs'; 
    local *a = "${context}::a"; 
    local *b = "${context}::b"; 
    return([sort $sort_function @_]); 
}