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