lexical for-in/for-of loose end

Jason Orendorff jason.orendorff at gmail.com
Fri Feb 3 12:44:28 PST 2012


On Fri, Feb 3, 2012 at 11:23 AM, Allen Wirfs-Brock
<allen at wirfs-brock.com> wrote:
> On Feb 3, 2012, at 6:42 AM, Jason Orendorff wrote:
>>    for (let V in EXPR) STMT
>>    for (let V of EXPR) STMT
>>    for (let V = EXPR; ...; ...) STMT
>
> 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.

The behavior of let-declarations is that the name hoists to the
enclosing block. We don't want to be *that* consistent.

So the situation ends up being: we will have a statement that looks
like "for(let V = ...)". It can either be partly consistent with other
statements that look like "for (let ...)" or partly consistent with
other statements that look like "let V = ...".

Either choice breaks with consistency on one side; TC39 should pick
the way that astonishes the fewest people.

>> 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.

Could you explain a little more why you think that's the case?

Grepping finds 1,227 loops of the "for (let ...; ...; ...)" variety in
Mozilla's codebase. Of these 95% are simple counting loops, and *none*
contain functions in the loop-head. I estimate about 4% of these 1,227
loops have functions in the body, most of which escape, but it is
harder to put precise numbers on that. Loops where the loop variables
are modified in the loop body seem rare (I didn't see any), but I
can't say how rare.

Of the non-simple-counting loops, here is a sample:

    for (let child = element.firstChild; child; child = child.nextSibling) ...
    for (let frame = aElement.ownerDocument.defaultView; frame !=
content; frame = frame.parent) ...
    for (let row; (row = aResultSet.getNextRow());) ...
    for (let i = 0; this[i] != null; i++) ...

In all these cases, per-iteration bindings would do the right thing.

If the choice comes down to astonishing this programmer:

   for (let i = 0; i < n; i++) {
       buttons[i] = makeButton();
       buttons[i].click(function () { alert("pushed button " + i); });
   }

or astonishing this one:

    let geti;
    for (let i=(geti = function() {return i},expr), expr, incr =
function(i++), decr=function(i--), cmp=function(){return i<n};
cmp();incr()) {
        let j=i;
        i +=10;
        decr();
        ...
        If (j+-9 !== i) ...
        ...
    }

then from where I'm sitting it looks like an easy choice. Let's
support realistic use cases.

Of course if per-iteration bindings would be confusing in practice,
that would be bad. But the for-loop head issue should not be a major
consideration, because people truly almost never write code like that.

I'd be glad to have range(); however I don't think it's effective to
address usability issues by offering more alternatives. :-\

-j


More information about the es-discuss mailing list