带有函数闭包规则的perl foreach循环

时间:2011-07-05 18:58:52

标签: perl foreach closures

以下代码

#!/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 个答案:

答案 0 :(得分:19)

问题是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>"

答案 1 :(得分:7)

Eric Strom的答案是正确的,可能是你想看到的,但没有详细介绍绑定。

关于词汇生命周期的简短说明:词汇在编译时创建,甚至在输入范围之前实际可用,如此示例所示:

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