2009-08-24 5 views
7

Итак, у меня есть класс Perl. Он имеет метод sort(), и я хочу, чтобы это было более или менее совпадает с встроенной функцией sort():Perl переменная scope вопрос

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

Но я не могу сделать:

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

Из-за определения объема. Но модуль List :: Util делает это с reduce(). Я просмотрел модуль List :: 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

+0

@cjm - Это правда, и я обязательно рассмотрю Sub :: Identify, но моя большая проблема заключается в том, чтобы заставить его работать вообще, а не заставить его работать в общем случае. Конкретные решения лучше, чем общие отказы. Однако объединение этого ответа с вашим вызовет мне общее решение, что хорошо. –

+1

Хотя получается, что 'sort' builtin имеет ту же проблему. Он предполагает, что функция сравнения исходит из того же пакета, что и вызывающий. Поэтому, если вы можете жить с этим, вы сохраняете зависимость от Sub :: Identify. (Или вы можете условно потребовать Sub :: Identify и вернуться к 'caller', если он не установлен. Но это больше работает.) – cjm

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

Он специально спрашивает о 'методе', а не о простом подпункте. –

+3

Это не сработает, если вы вызовете метод извне класса. Вы локализуете * класс * $ a и $ b, а не вызывающий. – cjm

+0

Ах. Я думал, что видел это, но, наверное, нет. Мое впечатление от perlvar заключалось в том, что '$ a' и' $ b' были достаточно малыми, чтобы они «просто работали» в этой ситуации. –

3

Вы можете использовать Sub::Identify, чтобы узнать об этом пакете (который он называет stash_name), связанном с coderef. Затем установите $ 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

Список :: Util просто использует 'caller'. Полагаю, этого недостаточно, в общем случае, не так ли? Если вызывающий абонент передает функцию из какого-либо другого пакета, тогда List :: Util будет устанавливать «$ a» вызывающего, а не функцию. –

+0

Версия List :: Util в 5.10.1 использует XS-код, который в основном делает то же самое, что Sub :: Identify делает, чтобы определить пакет, к которому принадлежит coderef. – cjm

+0

В качестве альтернативы, я мог бы переписать свой модуль в XS, потеряв при этом зависимость, и дал мне шанс изучить XS. Тем не менее, в то же время, я рассмотрю это. –