цикл perach perl с правилами закрытия функций - PullRequest
8 голосов
/ 05 июля 2011

следующий код

#!/usr/bin/env perl

use strict;
use warnings;

my @foo = (0,1,2,3,4);

foreach my $i (@foo) {
    sub printer {
        my $blah = shift @_;
        print "$blah-$i\n";
    }

    printer("test");
}

не делает то, что я ожидал.

Что именно происходит? (Я ожидаю, что он выведет «test-0 \ ntest-1 \ ntest-2 \ ntest-3 \ ntest-4 \ n»)

Ответы [ 2 ]

19 голосов
/ 05 июля 2011

Проблема в том, что конструкция sub name {...} не может быть вложена таким же образом в цикл for.

Причина в том, что sub name {...} действительно означает BEGIN {*name = sub {...}}, и начальные блоки выполняются, как толькокак они разобраны.Таким образом, компиляция и привязка переменной подпрограммы происходит во время компиляции, прежде чем цикл for получит шанс на запуск.

Вам нужно создать анонимную подпрограмму, которая будет связывать свои переменные во время выполнения:

#!/usr/bin/env perl

use strict;
use warnings;

my @foo = (0,1,2,3,4);

foreach my $i (@foo) {
    my $printer = sub {
        my $blah = shift @_;
        print "$blah-$i\n";
    };

    $printer->("test");
}

, который печатает

test-0
test-1
test-2
test-3
test-4

Предположительно, в вашем реальном случае эти замыкания будут загружены в массив или хеш, чтобы к ним можно было получить доступ позже.

Вы все еще можете использовать идентификаторы голых слов с замыканиями, но вам нужно проделать небольшую дополнительную работу, чтобы убедиться, что имена видны во время компиляции:

BEGIN {
    for my $color (qw(red blue green)) {
        no strict 'refs';
        *$color = sub {"<font color='$color'>@_</font>"}
    }
}

print "Throw the ", red 'ball';  # "Throw the <font color='red'>ball</font>"
7 голосов
/ 05 июля 2011

Ответ Эрика Строма правильный, и, вероятно, то, что вы хотели увидеть, но не вдаваться в детали привязки.

Краткое примечание о лексической продолжительности жизни: лексики создаются во время компиляции и фактически доступны даже до ввода их области действия, как показано в этом примере:

my $i;
BEGIN { $i = 42 }
print $i;

После этого, когда они выходят из области видимости, они становятся недоступными до следующего раза, когда они попадают в область видимости:

print i();
{
    my $i;
    BEGIN { $i = 42 }
    # in the scope of `my $i`, but doesn't actually
    # refer to $i, so not a closure over it:
    sub i { eval '$i' }
}
print i();

В вашем коде замыкание привязано к исходному лексическому $i во время компиляции. Однако циклы foreach немного странные; в то время как my $i фактически создает лексику, цикл foreach не использует его; вместо этого он связывает его с одним из зацикленных значений на каждой итерации, а затем восстанавливает его в исходное состояние после цикла. Таким образом, ваше закрытие является единственной ссылкой на оригинальную лексику $i.

Небольшое изменение показывает большую сложность:

foreach (@foo) {
    my $i = $_;
    sub printer {
        my $blah = shift @_;
        print "$blah-$i\n";
    }

    printer("test");
}

Здесь исходный $i создается во время компиляции, и замыкание связывается с этим; первая итерация цикла устанавливает его, но вторая итерация цикла создает новый $i, не связанный с замыканием.

...