# 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

Reply via email to