lexical for-in/for-of loose end

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Feb 3 09:23:56 PST 2012

On Feb 3, 2012, at 6:42 AM, Jason Orendorff wrote:

> On Thu, Feb 2, 2012 at 8:08 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
>> On Feb 2, 2012, at 5:07 PM, Jason Orendorff wrote:
>>> On Thu, Feb 2, 2012 at 5:52 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
>>> wrote:
>>>> for (let i=(geti = function() {return i},expr), expr, incr = function(i++),
>>>> decr=function(i--), cmp=function(){return i<n}; cmp();incr()) {
>>> What?! We should not reward people writing code like that.
>> No reward, it is simply what the current syntax permits, and it is important
>> that semantics are consistently applied.
> Oh, I definitely think we should have consistent semantics. It's
> difficult, because for(;;) is complicated, but it's worth the effort.
> Consider:
>    for (let V in EXPR) STMT
>    for (let V of EXPR) STMT
>    for (let V = EXPR; ...; ...) STMT
> I propose that in all three cases, the code that runs once, before the
> first iteration, namely EXPR, should be evaluated outside the scope of
> the loop variable. STMT should be evaluated within the scope of a
> per-iteration binding for V. The same semantics in all three cases.
> That seems to me both consistent and sensible.

the above is how I currently have it spec'ed for the first two cases.

the third case is different in several ways. Including:
   let V =EXPR is a distinct syntactic pattern that has a specific semantics  that requires that the EXPR is evaluated in the same scope as EXPR. However, in most cases (but consider |et x=(x=5,++x);) all of EXPR will be in V's temporal dead zone. 
   The actual syntax is for (let V1=EXPR1, V2=EXPR2,...;...) STMT Again, the semantics for such declarations is that all of the Vs are defined in the same scope and EXPRn+1 is outside the TDZ for for Vn.

Using your semantics for the 3rd form would mean that its let clause was not consistent with let declarations in all other contexts. 

> What is even more important than consistency is that the language work
> for people. We know that *not* having per-iteration bindings
> astonishes users, because that's how SpiderMonkey does it, and people
> are regularly astonished.

I think you are over generalizing from a specific use case.  This bug cuts both ways.

Programmers would always love to have a "do what I mean" language. When they are in the zone they often write code that reflects what they want to happen rather than what their language actually does. It's true that when programmers want a per-iteration binding they are astonished when they don't get one. However, it is also true, that when a programmer does not want a per-iteration binding they are astonished when they get one.  Both situations occur in practice, A programmer's intuition of the "right thing" may just be a reflection of which of these bugs most recently bit them.

For the sort of loops written using  for-in and for-of (with let/const bindings) I think we agree that there is a high probability that programmers will want a per iteration binding. 

However, it is a lot less clear that this is the case for the  for(;;) form.  This statement is one of the most idiosyncratic  syntactic forms of the the C syntax family of languages. Idioms for using it a generalized loop  construct (not just a simple counting loop) are well known.  Sometimes  parts of the loop logic that could be placed in a header expression are written in the loop body and sometimes code that could go into the body is placed in one of the header expressions.  Programmers who have experience with various C family languages have an expectation for how such for(;;) statements  work. Given this legacy and the variety of usage patterns it is a lot harder to generalize about  whether programmers will  usually want per loop or per iteration bindings.  

>> Plus, the desugarings aren't things that are really suitable for teaching
>> the semantics to everyday JS programmers.  You instead have to say something
>> like:
>> Ok, this is really complicated but here goes.  For each let/const declared
>> in the for header, a fresh variable is located in the loop body for each
>> iteration of the loop. However, the values of the loop variables are
>> automatically copied from the previous iteration into the next iterations.
>>  This means that basic expression operator will work in the loop header
>> pretty much like you would expect.  But be careful if you use any function
>> expressions in the for header because the loop variables they reference may
>> not be from the current iteration and any changes to loop variable they make
>> may not have the effect you intended.  But, hey you probably shouldn't do
>> those things so it really doesn't matter what it really does.
> This "explanation" seems oriented more towards making a rhetorical
> point than explaining. If I had to explain this to an everyday Web
> programmer, I would just write the three lines of code above and point
> out the consistent behavior. That way is simple, and it explains
> several things at once.

Yes, but you really wouldn't be explaining the behavior of the loop head expression.  It may be enough to get people started writing simply cases like the the one you show below but it really isn't teaching the actual semantics and its possible gotchas.
> Anyway, this kind of argument certainly isn't going to win me over,
> because the reason I want this change in the first place is so I can
> do less explaining! People do write code like this:
>    for (let i = 0; i < n; i++) {
>        buttons[i] = makeButton();
>        buttons[i].click(function () { alert("pushed button " + i); });
>    }
> It fails to work because in SpiderMonkey the binding isn't
> per-iteration. Then I have to explain. Per-iteration bindings mean
> less explaining, because fewer people will hit problems. Their code
> will just work.

But it works just like most other C family languages. Either way people sometimes will gets something that they didn't mean

Woundn't it be better to be able to teach people to write:

   for (let i of  range(0,n-1)) {
       buttons[i] = makeButton();
       buttons[i].click(function () { alert("pushed button " + i); });

People who are beginning programmers or who don't have a background with C syntax find the whole for (;;) thing intimidating anyway.  Rather than trying to fix for(;;) to DWIM, why don't we just offer a better alternative and deemphasize use of for (;;)


More information about the es-discuss mailing list