2011-07-05 53 views
8

đoạn mã sauperl foreach vòng lặp với chức năng đóng cửa cai

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

không làm những gì tôi mong đợi.

Chính xác những gì đang xảy ra? (Tôi hy vọng nó sẽ in ra "test-0 \ ntest-1 \ ntest-2 \ ntest-3 \ ntest-4 \ n")

Trả lời

19

Vấn đề là cấu trúc sub name {...} không thể lồng nhau như vậy trong vòng lặp for.

Lý do là vì sub name {...} thực sự có nghĩa là BEGIN {*name = sub {...}} và khối bắt đầu được thực thi ngay sau khi được phân tích cú pháp. Vì vậy, việc biên dịch và ràng buộc biến của chương trình con xảy ra tại thời gian biên dịch, trước khi vòng lặp for luôn có cơ hội để chạy.

gì bạn muốn làm là tạo ra một chương trình con nặc danh, trong đó sẽ ràng buộc các biến của nó trong thời gian chạy:

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

mà in

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

Có lẽ trong trường hợp sử dụng thực tế của bạn, những đóng cửa sẽ được tải vào một mảng hoặc băm để chúng có thể được truy cập sau này.

Bạn vẫn có thể sử dụng bareword định danh với đóng cửa, nhưng bạn cần phải làm một chút công việc phụ để đảm bảo tên có thể nhìn thấy tại thời gian biên dịch:

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
câu trả lời

Eric Strom là đúng, và có lẽ những gì bạn muốn xem, nhưng không đi vào chi tiết của sự ràng buộc.

Một lưu ý ngắn gọn về tuổi thọ từ vựng: lexicals được tạo ra tại thời gian biên dịch và thực sự sẵn sàng ngay cả trước khi phạm vi của họ được nhập vào, như ví dụ này cho thấy:

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

Sau đó, khi họ đi ra khỏi phạm vi, họ sẽ không khả dụng cho đến lần tiếp theo họ nằm trong phạm vi:

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

Trong mã của bạn, đóng cửa bị ràng buộc với từ ngữ ban đầu $i lúc biên dịch. Tuy nhiên, vòng lặp foreach hơi lạ; trong khi my $i thực sự tạo ra một từ vựng, vòng lặp foreach không sử dụng nó; thay vào đó, nó bí danh nó thành một trong các giá trị lặp trên mỗi lần lặp và sau đó khôi phục nó về trạng thái ban đầu của nó sau vòng lặp. Đóng cửa của bạn như vậy là điều duy nhất tham khảo từ vựng ban đầu $i.

Một biến thể nhẹ cho thấy phức tạp hơn:

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

    printer("test"); 
} 

Ở đây, bản gốc $i được tạo ra tại thời gian biên dịch và đóng cửa liên kết với đó; vòng lặp đầu tiên của vòng lặp đặt nó, nhưng vòng lặp thứ hai của vòng lặp tạo ra một $i mới không liên kết với việc đóng.

+0

rất thú vị, cảm ơn bạn – Snark

Các vấn đề liên quan