lexical for-in/for-of loose end

Grant Husbands esdiscuss at grant.x43.net
Mon Feb 6 10:46:38 PST 2012

Brendan Eich wrote:
> [Grant Husbands wrote:]
>> 'Note' all closures (dynamically) created in (lexically) the loop
>> initializer.
> Only in the initializer? Why should closures formed there be dynamically
> scoped to the current iteration?

Because that directly serves the use cases that have
for-initialization-based closures modifying loop variables. None of
the simple desugarings do that, beyond perhaps the first iteration.

> Instead [this requires] a dynamic scope for closures in the initializer
> Dynamic scope is a warning sign, almost always a mistake.

Indeed, given the eval shape-changing problem you pointed out, this
cannot be properly resolved, in the first form of this proposal. The
second form might not require generalised dynamic scope, though; it
requires an operation that clones a scope with a single environment
record changed, though. More on that, later.

>> Or, here's one that copies the other way (and is probably cleaner):
>> 'Note' all closures (dynamically) created in (lexically,
>> post-desugaring) the loop body. Each time you end an iteration, update
>> all the loop variable activation record pointers to point at a new
>> clone of that activation record.
> This is a more complex spec than one that models each iteration having its
> own lexical scope. The spec needs only declarative environments, not hidden
> references and pointer updates.

I think there may be a misunderstanding, as the operation I'm talking
about can be given in spec language without talk of references and
pointer updates. I'll write it out at the end of this email.

> As an implementation technique, Chez Scheme's heap boxing and assignment
> conversion could be even better.

Indeed, and that introduction of a level of indirection for affected
variables is what Herby was effectively suggesting, if I'm
understanding you both correctly. I was trying to suggest something I
thought was more compatible with the current specification.

>  But this is all beyond the spec.

I don't think it is. What Herby's idea and these formulations present
is a way for let-based loops to have modifications in closures
captured in the for-head that alter the loop variables in a way that's
visible to the current loop iteration. As such, choosing whether or
not to use these formulations affects the spec.

I agree that it may indeed be too large a feature, given that
desugarings can cover the vast majority of use-cases well enough. I
just thought it worth following the logic through.

> Still trying to be sure you intended a unique and dynamic scope for the
> initializer (first part) of for(let;;).

In the first version, depending on the definition of "dynamic", yes.
In the second version, no, though instead closures inside the loop
body get a similarly 'dynamic' scope.

Here's a longer, still informal version of the second.

Given a loop of this form:
for (let i = 0, inc = function(){i++}; i<N; inc()) { ... }
We want the loop to run to completion while ensuring that each closure
within the body gets a copy of 'i' as it existed in the loop iteration
in which the closure was created. One way to do that is to keep track
of the closures that get created during a loop iteration and, at the
end of the loop iteration, give those closures a new environment
([[Scope]]) which is a copy of the old one, but with a clone of the
current loop body environment record replacing the original.

In terms of spec: The 'add the closure to a list' operation could be
described using a desugaring. The 'scope clone with one record
changed' operation and the end-of-iteration use of it would require
spec changes.

In spec language, the main operation would probably be like this (I'm
cargo-culting the format, though, so I apologise if it has
idiosyncrasies): CopyDeclarativeEnvironment (E, C)
When the abstract operation CopyDeclarativeEnvironment is called with
a Lexical Environment
as argument E and a Lexical Environment as argument C the following
steps are performed:
1. Let env be a new Lexical Environment.
2. Let lastOuter be the outer lexical environment of E
3. If E and C are the same:
  a. Let envRec be a copy of the environment record of E.
  b. Set env's environment to be envRec
  c. Set the outer lexical environment reference of env to lastOuter
4. Else
  a. Set the environment record of env  to be the environment record of E
  b. Let outer be the result of calling CopyDeclarativeEnvironment
passing lastOuter and C as the arguments
  c. Set the outer lexical environment reference of env to be outer
5. Return env.

Probably the biggest issue with the above spec is that it assumes
envRec inside an environment is reference-like, which it could easily
not be in current implementations, so this variant would also
introduce an extra indirection in at least some circumstances.

I support the two main destructurings under consideration right now
more than I support the above, though.


More information about the es-discuss mailing list