Edit: Each time the loop is run, a new anonymous function is created. But in the "let" version, each of those functions is bound to a different version of "i", which means "i" is actually being re-declared as a new and separate variable every time the loop body runs?
You can have block scoping and your loops be scoped the other way around still:
r := []func() int{}
for i := range [5]int{} {
r = append(r, func() int { return i })
}
fmt.Println(r[0]()) // 4
r = r[:0]
for i := 0; i < 5; i++ {
r = append(r, func() int { return i })
}
fmt.Println(r[0]()) // 5
You can even have different loop types be scoped differently e.g. in C#, a C-style for loop will update the binding in-place but a foreach loop will not: var r = new List<Func<int>>();
for(int i=0; i<5; ++i) {
r.Add(() => i);
}
Console.WriteLine(r[0]()); // 5
r.Clear();
foreach(int i in Enumerable.Range(0, 5)) {
r.Add(() => i);
}
Console.WriteLine(r[0]()); // 0> so each iteration has a completely different binding.
That was what confused me about this rule as well: If you write "for (let i = 0; i<n; i++)", this will actually create n+1 independent variables, all named "i" but bound to different scopes.
The variables can even be independently modified:
You can write
for (var i=0; i<3; i++)
setInterval(() => {console.log(i); i+=100}, 1000);
This will print the "expected" output: 3
103
203
303
403
...
i.e. all the closures bound to the same variable. The same output is produced by let i; for (i=0; i<3; i++)
setInterval(() => {console.log(i); i+=100}, 1000);
However, if you write for (let i=0; i<3; i++)
setInterval(() => {console.log(i); i+=100}, 1000);
you get: 0
1
2
100
101
102
200
201
202
...
i.e. each closure logs - and increments(!) - its own copy of i.Neither "for" nor "let" on their own have this behaviour, it's a special language rule which seems to be triggered by using "let" in the first clause of a "for" statement.
I can understand the intention behind the design, but I feel that, in order to make a common usecase less surprising, they made the whole thing actually more surprising.
I don't agree with that, `for let` simply behaves as if the `let` was inside the loop:
for (var _i=0; _i<3; _i++) {
let i = _i;
setInterval(() => {console.log(i); i+=100}, 1000);
}
there you go, same behaviour as a for-let. for (let i=0; i<10; i++) {
console.log(i);
i++;
}
And it correctly logs "1 3 5 7 9".So if anything, the desugaring would have to be something like
for (var _i=0; _i<3; _i++) {
let i = _i;
setInterval(() => {console.log(i); i+=100}, 1000);
_i = i;
}
Haven't tried out putting closures inside the condition or incrementer to see if things get even weirder. for (initialiser; condition; incrementer) { body; }
initialiser;
while (condition) {
body;
incrementer;
}
This made perfect sense and was easy to reason about. Add lexical scoping, and it should obviously have just gained an extra level of scope, so that bindings are limited to the loop: {
initialiser;
while (condition) {
body;
incrementer;
}
}
But instead, the initialiser became awkwardly half inside the loop and half outside the loop: inside as regards lexical bindings, but outside as regards execution (… and lexical bindings in the remainder of the initialiser). That’s wacky. I understand why they did it, and in practice it was probably the right decision, but it’s logically pretty crazy, and much more convoluted for the spec and implementation. https://262.ecma-international.org/6.0/#sec-for-statement-ru... (warning: 4MB of slow HTML) and the following headings show how complicated it makes it. In essence, you end up with this: {
initialiser;
{
Lexically rebind every name declared in the initialiser.
(Roughly `let i = i;`, if only that shadowed a parent, as it does
in many such languages, rather than raising a ReferenceError.)
while (condition) {
body;
incrementer;
}
}
}
This reveals also problems in your attempted desugaring: for (var _i=0; _i<3; _i++) {
let i = _i;
setInterval(() => {console.log(i); i+=100}, 1000);
}
The trouble is that the condition and incrementer are not referring to a var, but rather to your lexical declaration that you put on the next line.Variable declarations with `var` should be avoided, linters commonly reject such code.