Perl переменная область видимости вопрос - PullRequest
7 голосов
/ 25 августа 2009

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

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

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

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

Из-за обзора. Но модуль List :: Util делает это с reduce(). Я посмотрел на модуль List :: Util, и они делают довольно неприятные вещи с no strict 'vars', чтобы это произошло. Я пробовал это, но безрезультатно.

Насколько я понимаю, reduce() работает так, как он работает, потому что он экспортируется в соответствующее пространство имен, и, таким образом, мой класс не может этого сделать, так как функция находится в другом пространстве имен. Это правильно, или есть какой-то (несомненно, более отвратительный и опрометчивый) способ сделать это в моей ситуации?

Ответы [ 3 ]

9 голосов
/ 25 августа 2009

Ну, два других ответа - полуправы. Вот рабочее решение, которое на самом деле сортирует:

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; Я уверен, что есть некоторые обручи, которые вы можете перепрыгнуть, чтобы как-то избежать этого.

4 голосов
/ 25 августа 2009

Вы можете использовать Sub :: Identify , чтобы узнать пакет (который он вызывает stash_name), связанный с coderef. Затем установите $ a и $ b в этом пакете, как требуется. Возможно, вам придется использовать no strict 'refs' в вашем методе, чтобы заставить это работать.

Вот ответ Евы, модифицированный для работы в общем случае:

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 голос
/ 25 августа 2009

Вы можете использовать оператор local для установки значений для $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);

Однако методы не подвержены влиянию прототипов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...