Allen Wirfs-Brock wrote:
On Feb 4, 2012, at 12:55 PM, Brendan Eich wrote:
>  The argument is as follows:
> > for (let i = 0, a = some.array, n = a.length; i< n; i++) { ... } > > here we definitely want the a in a.length (n's initializer) to be scoped by the head let -- to be the a declared by the second declarator. > > Now consider a bit of eta conversion: > > for (let i = 0, a = some.array, n = (function(){return a})().length; i< n; i++) { ... } > > It would be quite wrong for the a captured by the anonymous function expression to be other than the a binding declared and initialized immediately to the left.
Yes, I support the general principal that that initializers of bindings capture to the 
left.  But the problem here is that conceptually the let is defining multiple bindings 
(one per iteration).  I don't think many people are actually going to understanding the 
details of the proposed semantics and their implications.  Since most uses won't involve 
closure capture, any of the proposed semantics that have per iteration bindings with 
forward value propagation are just going to do the "right thing".  That is good.

That's all agreed, yes.

   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.


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.

But you're right that equivalence is lost for
      for(let x=n, y=x;;)...;
and
      for(let x=n, y={|| x}();;)...;

Whether this is better or worse than the "wrong capture" issue complete depends 
upon the actual programmer intent.

It's worse for the language's consistency of binding rules and equivalences. It's a botch. I think we should not equivocate here.


>  Users expect and even (now that they know, and Dart raises the ante) demand 
that each iteration gets fresh let bindings. Any who do capture an initial binding 
in a closure must know, or will learn, that it's just the first one, which fits 
the model.

For the latter, I strongly suspect that they won't know and will be WTF 
surprised when they encounter it.

Why? What binding did the think they captured, given the assumption we share (since you didn't reject it, we do share this, right?) that they know about fresh binding(s) per iteration? Are you implying users will expect the 0th-iteration bindings? Could be, but I rather suspect they'd want 1st. There isn't another choice. They don't want throwing upvars (throwing-up vars :-P).


>  Different how?

Different from subsequent iterations...

Take Jason's example

    for (let i = 0; i<  n; ) {
        setTimeout(...closure using i...);
        if (shouldAdvance())
            i++;
    }

If somebody decided to abstract the increment:

    for (let i = 0, advance={|| i++}; i<  n; ) {
        setTimeout(...closure using i...);
        if (shouldAdvance())
          advance();
    }

advance does what is intended if called on the first iteration, but not on 
subsequent one iterations. I'd soon get an error when I ran this than having it 
get stuck in a loop.

(I s/;/,/ on the for line to fix the typo.)

Anyone who writes that must be thinking the advance block-lambda is in the scope of the body, if they understand fresh-binding-per-iteration. But the advance block-lambda is not in the body. It's not before the loop either (wrong [outer] or no i in scope there). This is simply erroneous code by definition -- the assumption of fresh-binding-per-iteration having been learned already is violated by the structure of the code.

Rather than a dynamic error (might not test this), anyone writing this might rather get a static error. A warning would be justifed by a high quality implementation, based on analysis of the advance() call possibly coming from later iterations.

So here we return to Grant's thought of banning closures that capture loop control variables from the INIT part of for(;;) loops. We could do that, but I would want an early error. A closure could use eval to frustrate my desire for such an early error, but that would not make me want only a runtime error for all cases.

Anyway, I don't think we have enough evidence to make this an error case yet.

Well, we don't have a lot of evidence for any of this discussion. Does any C-syntax language currently implement for(;;) in the manner that is being proposed?

Dart, and for good cause: because JS with for(var;;) {... closure capture ...} is a design flaw that bites people over and over. It's why CoffeeScript added "do".

Actually, I'm  more concerned about un-savy users who won't really understand 
the full semantics, whatever we decide they are.

That's the wrong target of concern. http://wtfjs.org is full of stuff already, this is not even going to make the top ten, or top 100.

The crucial target of concern should be the savvy users and especially the semanticists and smarties who want consistent and minimal binding rules. I agree that error on loop variable capture from closure in INIT avoids doing violence to binding rules, but errors without justification do their own kind of damage. Rather have no errors and consistent binding rules, which is why I favor 1st iteration capture.

We should see if there are fuzz-testing cases that target JS1.7+ (Jason mentioned some) that would be broken by 1st iteration capture. Not because we cater to fuzzers, but to get a feel for the testable surface.

/be
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to