2011-07-05 18 views
8

następujący kodPerl foreach pętli z zamknięciem funkcji rządzi

#!/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"); 
} 

nie zrobić co by się spodziewać.

Co dokładnie się dzieje? (by się spodziewać, że do drukowania "test-0 \ ntest-1 \ ntest-2 \ ntest-3 \ ntest-4 \ n")

Odpowiedz

19

Problemem jest to, że sub name {...} konstrukcja nie może być zagnieżdżona w ten sposób w pętli for.

Powód jest taki, że sub name {...} naprawdę oznacza BEGIN {*name = sub {...}} i bloki rozpoczynają się natychmiast po ich przeanalizowaniu. Tak więc kompilacja i zmienne powiązanie podprogramu następuje w czasie kompilacji, zanim pętla for ma szansę na uruchomienie.

Co chcesz zrobić, to utworzyć anonimowej procedury, które będą wiązać swoje zmienne w czasie wykonywania:

#!/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"); 
} 

która drukuje

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

Przypuszczalnie w normalnym przypadku użycia tych zamknięć będzie być załadowane do tablicy lub skrótu, aby można było uzyskać do nich dostęp później.

Można nadal używać gołe słowo identyfikatory z zamknięciami, ale trzeba zrobić trochę dodatkowej pracy, aby upewnić się, że nazwy są widoczne w czasie kompilacji:

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
odpowiedź

Erica Strom jest poprawna, i prawdopodobnie to co Cię chciałem zobaczyć, ale nie wchodzę w szczegóły wiązania.

Krótka notatka o leksykalnym żywotność: lexicals są tworzone w czasie kompilacji i są faktycznie dostępne jeszcze przed wprowadzeniem ich zakres, jak w poniższym przykładzie:

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

Następnie, gdy udają się poza zakresem, oni stają się niedostępne aż do chwili, gdy będą w zakresie:

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(); 

w kodzie, zamknięcie jest zobowiązany do wstępnej leksykalnej $i w czasie kompilacji. Jednak pętle foreach są trochę dziwne; podczas gdy my $i faktycznie tworzy leksykalny, pętla foreach go nie używa; zamiast tego przypisuje ją do jednej z zapętlonych wartości każdej iteracji, a następnie przywraca jej pierwotny stan po pętli. Twoje zamknięcie jest więc jedyną rzeczą odwołującą się do oryginalnego leksykalnego $i.

Nieznaczne wahania pokazuje więcej Złożoność:

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

    printer("test"); 
} 

Tutaj oryginalny $i jest tworzony w czasie kompilacji i zamknięcie wiąże się z tym; pierwsza iteracja pętli ustawia ją, ale druga iteracja pętli tworzy nową $i, która nie jest powiązana z zamknięciem.

+0

bardzo interesujące, dziękuję – Snark