lexical for-in/for-of loose end

Grant Husbands esdiscuss at grant.x43.net
Tue Feb 7 06:40:57 PST 2012


On 7 February 2012 13:32, Andreas Rossberg <rossberg at google.com> wrote:
> What are the "closures that were created",

They are the closures that lie lexically within the loop's code,
outside of the init, (or, put differently, those that have the loop's
environment record in scope) and that were instantiated in the current
iteration.

> and how do you keep track of them?

In a linked list or array. Somewhere that sits with the loop state. In
desugaring terms, it could be expressed as an array, perhaps of weak
pointers, in the outer loop scope. I haven't got up to speed on Chez
Scheme's closures enough to answer this question for those.

> What should the following do?
> let a = [], b = [], c = [], d = []
> for (int i = 0, f = function(){ return function() { ++i } }; i < 10;
> d[i] = function() { return i }, ++i) {
>  a[i] = f;
>  b[i] = f();
>  c[i] = function(){ return i }
> }
> print(c[2]()); print(d[2]())
> a[4]()(); print(c[4]()); print(d[4]())
> b[7](); print(c[7]()); print(d[7]())

Here's what it does under the proposed scheme:
c[2]() -> 2
d[2]() -> 3
a[4]()() increments the i that is 10 to 11.
c[4]() -> 4
d[4]() -> 5
b[7]() increments the i that is now 11 to 12.
c[7]() -> 7
d[7]() -> 8
Basically, the closures lexically within the loop init always see the
latest version of i. Those lexically within the rest of the loop
always see the i that was available at the end of their iteration. The
iteration part of the loop is considered to be at the start. A related
gotcha is that d[9]() would return 12, if added to the end of that
sequence of calls. (I'm answering to the best of my ability, not
trying to defend anything.)

> And imagine the fun you can have with generators!

Indeed, you'd need to alter the [[ExecutionContext]] of suspended
generators whose [[Code]] lies within the loop (as well as altering
the [[Scope]]), which may be a bigger deal than just modifying
[[Scope]]s.

> Allow me to be blunt: this is literally raping the concepts of
> declarative environment and lexical closure. It is a hack, completely
> non-orthogonal,

I think that may be more than blunt; it is strongly emotive and also
vague enough that it can't really be answered. However, I and others
do share the concern that it may introduce too much complexity, and
then only really add support for a very rare usage.

> and I predict that the hidden statefulness it introduces to closure
> environments would be going to bite back in gore proportions.

It's a possibility, indeed. The variant that alters only the closures
lexically within the loop init has less chance of that, and could
potentially have restriction imposed, but all of these variants do
indeed introduce a way of (slightly) modifying the scope of a
particular group of closures after they have been created.

> Seriously, before we consider going there, I'd rather have a step on
> the break and stick to C-style for-semantics.

I think we wouldn't need to go that far; your generalization of Mark's
desugaring covers plenty of use cases and certainly the one of most
common loop/closure gotchas. To my understanding, that's the favoured
idea, so far, and we're just exploring this one to see where it might
lead.

Regards,
Grant Husbands.


More information about the es-discuss mailing list