lexical for-in/for-of loose end

Grant Husbands esdiscuss at grant.x43.net
Mon Feb 6 17:01:11 PST 2012


Brendan Eich wrote:
> I still do not think it's wise to specify in terms of such pointer-updating
> reference semantics, not for the body closures that want to capture loop
> control variables. But only if closures in the for-head capture loop
> variables? That would be Allen's "DWIM" (Do What I mean) semantics.

I think what you're saying here is that we would only ever need this
environment-copying trickery if there were closures in both the init
and in the rest, in which case I agree. Let-loops with closures only
in the init could then use the simplest semantics and let-loops with
closures only in the rest could use any of the desugarings. It's
starting to sound like a significant addition to the static semantics,
but I'll leave it to others.

Otherwise, maybe "Herby's init-swap", below, is what you're after? It
only alter closures in the init.

> Agreed, with caution about bending the body closure model around this
> prematurely. If we pull it off, probably the body closures can do the same
> "optimization" -- but it's not clear we can pull it off.

I think I'm not understanding something, here, as I'm still not sure
how the suggested semantics constitute even a quoted "optimization"
(maybe I'm not sure what it's an optimization of), or why we'd need to
bend both body closures and init closures in the same runtime, but
I'll carry on.

> Any preference for 0th over 1st iteration scope for closures in INIT binding
> initializer expressions?

If the init binding's closures are not going to touch every one of the
iterations, I believe they should touch none of them, for consistency
(I think that means "0th"). But I don't feel strongly about it, and it
does look like implementation detail might favour "1st".

[snipped implementation details]
> is not going to give TEST and NEXT the "DWIM" semantics. Sorry, I'm
> sure you get this, I'm just spelling it out to be sure everyone (including
> me) gets it.

I indeed agree; TEST and NEXT are essentially part of the body, since
they have always needed to reference the body's iteration variables
and are repeated alongside the loop body. And, to spell things out
further, it's the mechanism of "reenterblock" for these loops that
this thread is discussing, as I understand it.

> Why should they form closures that magically reference "current
> iteration scope" when called later?

Indeed, closures in TEST and NEXT must clearly be instantiated per
iteration and they lie naturally in the body, under all intuitive
desugarings, so they might as well be treated in the same manner as
the body.

> DWIM always falls to ambiguity. What did you mean? I dunno, just do it! :-P

Indeed, and individual and sometimes obtuse use cases can have more
influence than they perhaps should. (That's not a sly jab at anyone.)

Based on my reading of what you wrote, but without being able to point
at any one particular quote, I think there may still be a
misunderstanding of some kind, so I think we might benefit from me
spelling out a run-through under the versions of Herby's idea covered
so far. Note that I can't guarantee that this is a correct formulation
of Herby's idea, but I am fairly sure it is. Also note that each
formulation is entirely distinct. We'd never use both in the same
runtime.

In each case, the example code is as follows:
for (let i=0, inc=function(){++i};i<2;inc())
 setTimeout(function(){alert(i)},200);

Herby's formulation (and my second variant), which I'll call "Herby's
body-swap", would proceed as follows:
1. Scope with i and inc created for the loop.
2. Loop starts.
2a. The inc() isn't run, since this is the first time round the loop.
3. i<2, so we carry on.
4. The body closure gets created and points at the active i, which is 0.
5. The iteration ends and the next one starts.
5a. The body closure that was created gets cloned variables, with i==0.
5b. inc(), so i becomes 1.
6. i<2, so we carry on.
7. The body closure gets created and points at the active i, which is 1.
8. The iteration ends and the next one starts.
8a. The body closure that was created gets cloned variables, with i==1.
8b. inc(), so i becomes 2.
9. i<2 is false, so we're done.

At this point, we have two internal closures, with i==0 and i==1, and
the inc closure can see i==2, which is indeed the value i would have
after the loop was over, which seems intuitive.

The mechanism for cloning loop variables could be like any of Herby's
suggestions or the CopyDeclarativeEnvironment operation I suggested.
Note that that variant only needs one scope for the whole loop, though
the cloning operation creates its own.

My other formulation, which I'll call "Herby's init-swap", would
proceed as follows:
1. Scope with i and inc created for the loop.
2. Loop starts
2a. The loop's implicit internal let block starts and copies i and inc.
2b. The inc gets updated to point at the current iteration scope.
2c. The ++i isn't run, since this is the first time round the loop.
3. i<2, so we carry on.
4. The closure gets created and points at the inner i, which is 0.
5. The iteration ends and the next one starts.
5a. The loop's implicit internal let block copies values from the end
of the last
5b. The inc gets updated to point at the current iteration scope.
5c. inc(), so i becomes 1.
6. i<2, so we carry on.
7. The closure gets created and points at the inner i, which is 1.
8. The iteration ends and the next one starts.
8a. The loop's implicit internal let block copies values from the end
of the last
8b. The inc gets updated to point at the current iteration scope.
8c. inc(), so i becomes 2.
9. i<2 is false, so we're done.

At this point, we have two internal closures, with i==0 and i==1, and
the inc closure can see i==2. So the visible behaviour is the same either way.

Now, there are some advantages and disadvantages:
* Herby's body-swap gives an apparent desugaring with just one scope
for the loop variables
* Herby's init-swap might need to be concerned about shape changes
(due to eval or such).
* Herby's init-swap has a consistent set of closures to alter on each
iteration (barring nested closures in the init).
* Herby's init-swap's variables/records that need replacing are
incredibly likely to be topmost in the closure's scope and could be
specced as such (which would make it favourable to current runtimes,
afaik).
* Both of them pretty much require some form of scope cloning or scope
modification that does not match anything currently in the spec.
* Both of them make closures in the init and in the rest see a
consistent world, for as long as is possible, while still giving body
closures unique copies per iteration.

Sorry for the long email, but I thought the detail could help iron out
any of the remaining (lesser) misunderstandings.

Regards,
Grant.


More information about the es-discuss mailing list