Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

perl foreach loop with function closure rules

the following code

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

does not do what I would expect.

What exactly is happening? (I would expect it to print out "test-0\ntest-1\ntest-2\ntest-3\ntest-4\n")

like image 912
Snark Avatar asked Jul 05 '11 18:07

Snark


2 Answers

The problem is that the sub name {...} construct can not be nested like that in a for loop.

The reason is because sub name {...} really means BEGIN {*name = sub {...}} and begin blocks are executed as soon as they are parsed. So the compilation and variable binding of the subroutine happens at compile time, before the for loop ever gets a chance to run.

What you want to do is to create an anonymous subroutine, which will bind its variables at runtime:

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

which prints

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

Presumably in your real use case, these closures will be loaded into an array or hash so that they can be accessed later.

You can still use bareword identifiers with closures, but you need to do a little extra work to make sure the names are visible at compile time:

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>"
like image 190
Eric Strom Avatar answered Sep 29 '22 12:09

Eric Strom


Eric Strom's answer is correct, and probably what you wanted to see, but doesn't go into the details of the binding.

A brief note about lexical lifespan: lexicals are created at compile time and are actually available even before their scope is entered, as this example shows:

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

Thereafter, when they go out of scope, they become unavailable until the next time they are in scope:

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

In your code, the closure is bound to the initial lexical $i at compile time. However, foreach loops are a little odd; while the my $i actually creates a lexical, the foreach loop does not use it; instead it aliases it to one of the looped over values each iteration and then restores it to its original state after the loop. Your closure thus is the only thing referencing the original lexical $i.

A slight variation shows more complexity:

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

    printer("test");
}

Here, the original $i is created at compile time and the closure binds to that; the first iteration of the loop sets it, but the second iteration of the loop creates a new $i unassociated with the closure.

like image 20
ysth Avatar answered Sep 29 '22 12:09

ysth