On Thu, Jul 10, 2008 at 11:06:55PM -0500, Patrick R. Michaud wrote:
> 
> I _think_ [methods and subs] are the only two cases where we 
> have to do something like this, and I guess they aren't too onerous.  

I was wrong, the third case is MultiSubs, and at the moment
it's _very_ onerous, because we can't just replace things in 
the symbol table.

But having given myself permission to take a break, I think I may
have come up with a workable answer that does almost everything
we (both) want.  I'll try to phrase it in the terminology you've
been using (and apologies if I slip up).

If I understand your messages correctly, the main problem with
"autoclosures" is the "auto" portion -- i.e., trying to capture
variable bindings automatically at the time a closure is invoked, 
and doing this only once on the closure's first invocation.  
Let's agree that this behavior is wrong.

Let's go even further and explicitly make this an error, such that
invoking a Closure PMC where its outer_ctx is null throws an 
exception.  

However, I'd still like the capability to give an existing Closure
an outer_ctx without having to replace it in the symbol table,
method table, or MultiSub PMC.  Thus "newclosure" doesn't really
do what I want, because it always creates a new Closure PMC,
and then I have to somehow get that new PMC into its correct
sub/method/multisub location.  What I really want is a way to 
give an existing Closure PMC an outer_ctx (but not automatically 
as part of invocation).

So, I propose that we add a "capture" method to Closure PMCs
to set the outer_ctx for that PMC.  This is roughly 
equivalent to what 'newclosure' does now, but doesn't clone 
the Closure PMC prior to capturing the variable bindings.

This capture method would be called from the outer sub at whatever
point we would otherwise be doing a "newclosure+store" operation.
But now since we're not creating a new PMC, we don't have to
worry about trying to fix up the symbol table, method table,
or MultiSub entry that references the current Closure.

(Side note:  we _could_ make this a 'capture' opcode instead of a method,
but I'm not a big fan of opcodes that only work on one PMC type.
In fact, I often wonder if the 'newclosure' opcode should
really be a method on Capture, since (at present) it only works 
on Capture PMCs.  OTOH, perhaps an opcode is much faster than
a method call, and perhaps capture/newclosure warrant speed here.)

In fact, with a 'capture' method or opcode, we might not need 
'newclosure' at all.  A newclosure operation could potentially 
be done by:

    $P0 = clone $P0         # clone the Capture PMC
    $P0.capture()           # capture outer context into the clone

But this is a feature, and we can certainly keep 'newclosure'
around if we want to do a clone+capture sequence.

Just having something like the capture method described above seems 
like it would solve my problems without resorting to autoclosures.
The PIR for my Perl 6 example could then look something like:

    .sub 'foo'
        .param pmc x
        .lex '$x', x
        .const 'Closure' inner = 'inner'
        ##  capture variable bindings for inner
        inner.'capture'()
        print "outer foo "
        say x
        'inner'()
    .end        

To repeat, in this scenario, invocation never does a capture on its
own -- that's always up to the outer sub to handle by doing either
a newclosure or capture operation prior to invocation.

If you and others think this is workable, then this is good
enough for me.  But there's more.  :-)

Instead of having Parrot do autoclosures -- i.e., automatically
capture outer_ctx when an inner Closure is invoked -- perhaps
we could have a way for an outer sub to automatically perform
the captures for all of its inner subs.  In other words, every
sub would maintain a list of its inner subs (automatically
created from the inner subs' :outer flags), and then the
outer sub can automatically set the outer_ctx for all of
its inner closures.  This could happen either as part of
the outer sub's invocation, or it could be an explicit
instruction or method call generated by the HLL compiler.

Based on what Bob has been saying, I can't now think of a
case where an inner closure _shouldn't_ go ahead and have
its outer_ctx set whenever an outer sub is invoked.
There might be a small optimization to be had if we can
determine that some inner closure can't be invoked
(and thus we don't need the capture operation), but
I'm thinking that the cost of immediately assigning
outer_ctx to all inner subs is far less than doing
separate newclosure+store operations for each.
(Perhaps there's another reason this approach can't work.)

Anyway, that's my proposal for now -- feel free to
shoot holes in it or tell me where I've overlooked something
or misunderstood closures once again.  :-)

Pm

Reply via email to