lexical for-in/for-of loose end

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Feb 6 10:45:39 PST 2012


On Feb 4, 2012, at 6:35 PM, Brendan Eich wrote:

> Allen Wirfs-Brock wrote:
>> On Feb 4, 2012, at 12:55 PM, Brendan Eich wrote:
>>> ...
> 
>>   However, I doubt that someone who actually codes a function in a for(let;;) initializer is going to be thinking, "of course, this only captures the first iteration bindings".
> 
> Why? The initializer runs first. There are two working possibilities: bindings of the same name in an implicit block around the loop (the "0th iteration scope"), immediately shadowed on first (if the condition is true) by fresh bindings of the same names with values forwarded; or what I propose, the first iteration's bindings.
> 
> Why is the 0th iteration scope better than the 1st iteration scope? It may be that some people think of the INIT part in for (INIT; TEST; UPD) STMT as being "before the loop starts", but others may see it as part of the first iteration.
> 
> If we really do have evidence for the 0th iteration scope (hard to imagine), then that is doable but it costs a bit more in the closure-in-initializer case. All else equal I go with lower cost in that rare case.

In general, the concerns we have to consider here relate to when programmers think incorrectly about the for(;;) semantics, not when they are thinking correctly.

Why, isn't the programmer thinking about per iteration bindings? Because if somebody is actually doing something like the |advance={|| i++}| example, there is a good chance that the loop body doesn't contain any closure captures of the loop variables and they are likely to be thinking about it as if it had classic C for(;;) semantics.

In the case where there are no closure captures in the body we probably all expect implementations to actually share a single set of bindings across all iterations. If an implementation did that for this case it would yield the result the programmer to expecting.  But if an implementation actually did that in this case it would be an invalid optimization because of the proposed bind to only first iteration semantics.

I think it may be useful to enumerate the cases of interest, of a for(let;;) loop that we abstract to: for (LET-BINDINGS=INITRS;TEST;NEXT) BODY

1) No closure captures of LET-BINDINGS in any part of the for
          Probable programmer intent: per loop binding of  LET-BINDINGS, but that is indistinguishable from the expected optimization of per iteration binding 
2) BODY contains closure captures of LET-BINDINGS; no closure capture of LET-BINDINGS in INITRS;TEST;NEXT.
          Probable programmer intent: per iteration bindings captured
3)  No closure captures of LET-BINDINGS in BODY; closure capture of LET-BINDINGS in one or more of  INITRS;TEST;NEXT.
          Probable programmer intent: per loop binding of  LET-BINDINGS, but this is not a valid optimization of per iteration binding using either the 0th iteration or 1st iteration scoping semantics
4)  BODY contains closure captures of LET-BINDINGS; at least one of  INITRS;TEST;NEXT   contains closure captures of LET-BINDINGS.
          Probable programmer intent: ambiguous. Only consistent interpretation is per loop binding of  LET-BINDINGS, but high probably for a per iteration expectation plus buggy thinking.

For me, a couple things stand out from this analysis
*    Testing for capture of specific bindings seems like a pretty straightforward static  semantics.  It seems quite doable to make case 4 an early error. 
*    It is closure captures in BODY that wants per iteration instead of per loop.  Otherwise programmer intent is probably per loop.
*    Cases 1 to 3 would all DWIM If we had a semantics that could be validly optimized to per loop bindings for case 3.  But I haven't found such a semantics.

> 
>> With the TDZ alternative I proposed, there  would still be equivalence for:
>>      for(let x=x;;)...;
>> and
>>      for(let x={|| x}();;)...;
>> 
>> Both throw for accessing an initialized variable.
> 
> Yippee. :-|
> 
> Making a dynamic error trap for no good reason is a mistake. Where's the evidence that closures in initializers that capture loop control variables are "wrong"? There isn't any, AFAIK.

The dynamic trap is simply the TDZ semantics.  Whether capture initializer capture of loop var is wrong or not completely depends upon the intended use in the context of the actual semantics.  I haven't yet thought of any plausible use cases where where the intent is first iteration capture.  

But, as noted above a early error for such captures may be a viable alternative.

Allen
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120206/c9c1671f/attachment-0001.html>


More information about the es-discuss mailing list