lexical for-in/for-of loose end

Allen Wirfs-Brock allen at wirfs-brock.com
Sat Feb 4 10:51:08 PST 2012

On Feb 4, 2012, at 9:49 AM, Brendan Eich wrote:

> I want off this merry-go-round! Let's recap:
thanks for the recap...

> ...
> From the January 19th 2012 notes Waldemar took:
> Discussion about scope of for-bindings.
> for (var x = ...;;) {...}  will, of course, retain ES1 semantics.
> for (let x = ...;;) {...}
> Allen: This will behave as in C++: x is bound once in a new scope immediately surrounding just the for statement.
> DaveH: Strangely enough, this creates a new x binding in Dart at each iteration.
> There's an alternative semantics that creates an iteration-local second x inside the loop and copies it back and forth.  Debate about whether to go to such complexity.  Many of us are on the fence.
> Waldemar: What happens in the forwarding semantics if you capture the x inside a lambda in any of the three expressions in the head?
> If this happens in the initializer:
> DaveH's option: The lambda would capture an outer x.
> Alternative: The lambda captures a hidden second x.
> Waldemar's option: The lambda would capture the x from the first iteration.  The let variable x is bound once through each iteration, just before the test, if
> ...

> --- end grant citation ---
> Ok, Brendan here. I agree we want to support multiple declarators for the let or const in the for-head's initialization part.

> I agree we want to capture the first-iteration bindings in any closures in those declarators' initializers.

It isn't clear to me why capture first-iteration is abstractly any better than "capture a hidden second x".  In both cases, in most iterations of the loop, evaluation of any such captures is going to reference the "wrong" binding.  From a user perspective, the main advantage I see for capture first iteration is that it has a slightly smaller window of wrongness.  The captures evaluated in the first iteration will reference the correction binding, while latter iterations reference the wrong binding.  From an implementation perspective, it is probably a bit simpler to not have the extra hidden binding for capture.

There is another alternative that I haven't seen mentioned.  Scope any closures in the initialization expression such that any references to loop declared binding resolve to undeclared bindings,

For example (using block lambdas for conciseness)
   for( let d1={|| d1}, d2={|| d1+d2};;)...

would actually be interpreted as if it was
   for (let d1={|| return {|| d1}; let d1,d2}(), let d2={|| return {|| d1+d2}; let d1,d2}();;)...

In words, any references to loop declarations capture uninitialized bindings that never get initialized.  If evaluated they will throw as accesses within the TDZ of the binding.

Always making such bindings (when used) a error seems preferable I(from a user perspective) than making the first iteration of a loop behave differently from subsequent iterations.

> ...

> Mutations to the first-iteration d1, ... dN bindings in any closures in e1...N propagate to the second iteration.
> Is this enough to restore the consensus we thought we had at the end of the meeting day on January 19th?

I really don't like the first iteration is different semantics and think we should think about the above alternative.

However, such closure capture is very rare (could use of block lambda based patterns change that??) so it may come down to judgements about implementation costs.  Is capture first going to be significantly easier to implement than my alternative scoping? The answer is obvious to me.  In either case an implementation is like to special case loops with closure capture in their initializers.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120204/11fafa10/attachment.html>

More information about the es-discuss mailing list