# New Ticket Created by Bob Rogers # Please include the string: [perl #44395] # in the subject line of all future correspondence about this issue. # <URL: http://rt.perl.org/rt3/Ticket/Display.html?id=44395 >
The problem arises when blocks with closed-over lexical bindings are executed more than once, as can happen inside loops. Here is an example in Perl 5: sub make_closures_loop { # Return $n closures, each with lexical references to $i and $n. my $n = shift; my @result; for (1..$n) { my $i = $_; push(@result, sub { print "Called sub $i out of $n.\n"; }); } return @result; } Here there is only one $n binding, but a new and independent $i binding is created every time through the loop, as running the first attachment clearly shows. It makes no difference if you say "for my $i ..." or replace the loop with "map" instead. A naive PIR implementation of this loop is included as the second attachment. It fails miserably, because PIR can't distinguish between the different scopes for "$i" and "$n". Currently, the only way to get a distinct binding for each "$i" in PIR is to factor the loop body out into a separate lexical sub. This is far from ideal, not least because it is not transparent to the HLL user. It is also painful for compilers to optimize away: In order to decide whether the extra sub is really needed, the compiler must find all the closed-over variables first, which (for me at least) requires breaking lexical analysis into two passes. Parrot should do better, IMHO. The easiest way, it seems, would be to resurrect the push_pad and pop_pad instructions in some form. After the lexical analysis, the compiler can simply use the same "is this closed-over binding inside a loop?" analysis to decide whether or not it needs to emit push_pad/pop_pad at the appropriate places. It then becomes purely a question of merging lexical scopes during optimization, and doesn't affect semantic correctness. FWIW, I do remember waaay back when Leo got rid of the explicit pad manipulation instructions (r10240 on 2005-11-29 and thereabouts). At the time, it went right over my head that I might ever need these (my compiler didn't even support closures until three weeks later), for which I am now very sorry. I am hoping that others will have a similar reaction, and that push_pad etc. weren't flushed due to some fundamental problem. For the record, continuations are also broken in this respect; not surprisingly, they'll take you back to the right context, but with whatever value of "$i" was stored last. So whatever solution is adopted, we need to make sure that continuations capture the detailed lexical state. Any objection if I start working on this? TIA, -- Bob Rogers http://rgrjr.dyndns.org/
#! /usr/bin/perl -w use strict; use warnings; sub make_closures_loop { # Return n closures, each with lexical references to $i and $n. my $n = shift; my @result; for (1..$n) { my $i = $_; push(@result, sub { print "Called sub $i out of $n.\n"; }); } return @result; } # Make three closures and call them in turn. for my $c (make_closures_loop(3)) { $c->(); }
## Return n closures, each with lexical references to "I" and "N'. .sub make_closures_loop .param pmc n .lex "$n", n .lex "$i", $P42 .local pmc result result = new .FixedPMCArray $I1 = n result = $I1 .const .Sub $P53 = 'internal_make_closures_loop_0' $I0 = 0 next: if $I0 >= $I1 goto done $P42 = new .Integer $P42 = $I0 inc $P42 newclosure $P52, $P53 result[$I0] = $P52 inc $I0 goto next done: .return (result) .end .sub internal_make_closures_loop_0 :outer('make_closures_loop') find_lex $P42, "$i" find_lex $P43, "$n" print "Called sub " print $P42 print " out of " print $P43 print ".\n" .end ## Make three closures and call them in turn. .sub test_closures :main .local pmc closures $I1 = 3 closures = make_closures_loop($I1) $I0 = 0 next: if $I0 >= $I1 goto done $P52 = closures[$I0] $P52() inc $I0 goto next done: .end