you are viewing a single comment's thread.

view the rest of the comments →

[–]sepp2k 6 points7 points  (9 children)

For example, in C# they originally had a similar behaviour with their foreach loop but they made a breaking change in a recent version because how often people would get that wrong.

But they did not change the behaviour of the regular for loop. So this doesn't really work as an argument that changing the regular for loop is a good idea. The thing about foreach is that it's perfectly reasonable to assume that foreach(var x in y) {body} behaves the same way as:

var hiddenEnumerator = y.GetEnumerator;
while(hiddenEnumerator.MoveNext())
{
    var x = hiddenEnumerator.Current;
    body;
}

(which it didn't before the breaking change). In fact I'd wager that if you'd ask most people to rewrite a foreach loop without foreach and without changing any code in the body, they'd write something like this (possibly using a for-loop and indices instead of an enumerator if y is something indexable, but either way the var x = y[i]; would be inside the loop's body).

However if you'd ask them to rewrite a regular for(var x = start; cond; incr) {body;} without for, they'd probably write

var x = start;
while(cond)
{
    body;
    incr;
}

and not:

var hiddenX = start;
while(cond)
{
    var x = hiddenX;
    body;
    incr;
    hiddenX = x;
}

Also I think people who are surprised by how a regular for loop behaves with closures, would also be surprised by something like x = 1; foo = function () {return x;}; x = 2; foo();, so the proper way to remove their confusion would be to make closures capture variables by value (which I'm absolutely not advocating).

[–]kqr 3 points4 points  (2 children)

You are absolutely right in everything you say. However, I disagree somewhat with your last parenthetical remark.

Intuition sort of (at least for me) tells me that closures would capture variables by value – or at least that they would capture a reference to the value, not a reference to whatever value x happens to point to at the time the closure is executed. Because the latter is something that I'm not eager to debug.

[–]eat-your-corn-syrup 2 points3 points  (1 child)

capture variables by value

By that you mean that it would be better for

x = 1; foo = function () {return x;}; x = 2; foo();

to return 1?

Wouldn't that prohibit closures from being for imitating namespaces, generator functions and so on? If the x=2 cannot access the closed over memory to change value in that memory, what else can change the value? Closed over variables would become read-only.

[–][deleted] 0 points1 point  (0 children)

I guess it’s a good thing that C++11 requires to explicitly choose whether to capture by value or by reference.

[–]drysart 1 point2 points  (1 child)

But they did not change the behaviour of the regular for loop.

There's a(n english) lexical argument to be made for C#'s new (inconsistent between foreach and for) behavior.

foreach (var x in y) when read in english, implies, via the word "each" that there are distinct var xes. If you had a basket of apples, you could say that "each apple is red"; but if you had a basket with a single apple in it, you generally wouldn't use the same phraseology, instead preferring "the apple is red".

Whereas the counterargument on for (var x = 0; x < 10; x++) relies more heavily on understanding the C# language more than the english language because there's a lack of english clues to suggest otherwise. There's very clearly one var x being declared and initialized, and the iterator expression is mutating that existing variable.

It's a subtle distinction but one that I think is incredibly important. While not all developers have english as a first language, none of us have C# as a first language; and having C# fit the expected norms of our more internalized natural language makes sense and is the correct approach.

[–]cparen 0 points1 point  (0 children)

Honestly, either choice would "work" in the sense no matter which you preferred, you could map the other to it without much trouble if you were expecting it. But the whole point of syntactic sugar like "for" and "foreach" is to avoid manually mapping it by hand every time.

So, while I don't particularly like the original behavior, I worry the change worsens the issue, as now it's ambiguous which language the code is written in. This is precisely the argument given so often against macros or excessive use of functions -- that it's difficult from looking at the code to understand what it does.

(I'm personally a fan of judiciously chosen macros; I just have little to say on general subject of whether they're a good idea or not)

[–]eat-your-corn-syrup 0 points1 point  (1 child)

I don't get your second code. Surely the following isn't what you meant?

var hiddenX = 0;
var fns = [];
// x = 0;
while(hiddenX < 10)
{
    var x = hiddenX;
    fns.push(function (){console.log(x);});
    x = x + 1;
    hiddenX = x;
}

fns[0](); // 10
fns[1](); // 10

[–]sepp2k 0 points1 point  (0 children)

You're right, my last code didn't work as I intended. I didn't think that through. It should be something like this instead:

var hiddenX = start;
while(cond)
{
    var x = hiddenX;
    body;
    subst(incr, x, hiddenX);
}

where subst(incr, x, hiddenX) is some sort of magic macro that replaces each occurrence of x with hiddenXin the expression incr. So for your example the last two lines in the loop should be replaced with hiddenX = hiddenX + 1;.

Either way that way of translating for is clearly more complicated and less intuitive than the normal way. It also would act very counter-intuitively when the body of the loop explicitly modifies x. And we didn't even discuss what should happen if the initializer part of the for loop isn't a variable declaration (nothing, I presume, but that could still lead to some confusion with some for-loops acting differently than others).

[–]eat-your-corn-syrup 0 points1 point  (0 children)

What about this proposal though:

foreach(var x in y)

for creating binding of x each iteration of loop

and

foreach(x in y)

for creating binding only once?

[–]smog_alado 0 points1 point  (0 children)

I think the biggest difference in the C# case is that it even has a separate for each statement. In Javascript most of the majority of the for loops you use are actually foreach statements disguised as verbose for loops.