I think the main things that make it such a trap is that the variable type definition is implicit so the fact that it's a pointer becomes a bit hidden, and that easy concurrency means the value is evaluated outside of the loop execution more often.
I think the main things that make it such a trap is that the variable type definition is implicit so the fact that it's a pointer becomes a bit hidden, and that easy concurrency means the value is evaluated outside of the loop execution more often.
That might be the case, but my comp sci 101 was 15 odd years ago now and since then I have _never_ had to think about pointers vs values, until I started a Go project a few years ago. But even that was more comprehensible than the pointer wizardry we had to do in C/C++ back when.
I don't want to have to think about managing my application's memory, I much prefer being in the code, thinking of variable scope and maintainability which in a lot of languages automatically translates to healthy memory usage.
No. Full disagree.
Array represents a concept of holding multiple values (let's simplify) of the same type.
Loop (not index based) over array represents concept of going *over* array's elements and executing some code body for each of it.
Now, if the behaviour isn't that loop's body is executed for each array element (let's forget about returns, breaks, etc)
then the design is terrible (or implementation, but that'd mean that it was a bug)
I have totally no idea how can you design this thing in such a unintuitive way unless by mistake/accidentally.
Basically, here are two pseudocode implementations. This is what currently happens:
i = malloc(sizeof(int))
*i = 0
loop:
<code>
*i = *i + 1
goto loop if *i < 10
This is what people expect: secret = malloc(sizeof(int))
*secret = 0
loop:
i = malloc(sizeof(int))
*i = *secret
<code>
*secret = *secret + 1
goto loop if *secret < 10
You can see that they are not crazy for picking the first implementation; it's less instructions and less code, AND the for loop is pretty much exactly implementing what you're typing in. It's just so easy to forget what you're actually saying that most languages are choosing to do something like the second example (though no doubt, not allocating 8 bytes of memory for each iteration).Remember, simple cases work:
for i := 0; i < 10; i++ {
fmt.Println(i) // 0 1 2 3 4 ...
}
It's the tricky cases that are tricky: var is []*int
for i := 0; i < 10; i++ {
is = append(is, &i)
}
for _, i := range is {
fmt.Println(*i) // 9 9 9 9 9 ...
}
If you really think about it, the second example is exactly what you're asking for. You declared i into existence once. Of course its address isn't going to change every iteration.Loop in general or "for each" style loop, that's huge difference.
The 2nd one has a lot to do with collections.
>You can see that they are not crazy for picking the first implementation; it's less instructions and less code
Yes, it is not crazy when you're looking at it from the reverse engineering / implementation side
but if you start thinking about it from user's perspective then it is very bad behaviour
because they used "foreach" like loop which is a concept of walking thru every element of collection.
Normal "for" is like: repeat this code body as long as condition is satisfied
Foreach is more like: walk thru this collection
Look (c#):
foreach (var item in items) ...
for (int i=0; i<10; i++) { }
In the 2nd version it is possible to jumps ahead, back, do not move, etc. Generally play around "i's" values
Meanwhile I haven't seen yet any1 trying to do anything like this in foreach, because it is meant for just walking thru collection
The solution as I said elsewhere is to pop out the inner block to a separate function, where the value of the counter is captured when the outer function is called, not when the inner one runs.
for (int i = 0; i < n; i++) {
callbacks.add(new Callback(){
public void Call() {
System.out.println(i); //compiler error: local variables referenced from an inner class must be final or effectively final
}
});
}
for (var f : callbacks) {
f.Call();
}
Note that code like this works, and does the expected thing: for (int i : new int[]{0, 1, 2}) {
callbacks.add(new Callback(){
public void Call() {
System.out.println(i);
}
});
}
for (var f : callbacks) {
f.Call();
} //prints 0 1 2The fix is to make a new variable for each iteration, which is less obvious implementation wise but as per the post works better if you're enclosing over the loop variable.
for i, v := range []int{1, 2, 3} {
funcs = append(funcs, func() {fmt.Printf("%v:%v, ", i, v)})
}
for _, fun := range funcs {
fun()
} //prints 2:3, 2:3, 2:3
The reason why this happens is clear. But, it's not what people expect from the syntax, not at all. And it's also a behavior that is never useful. There is 0 reason to capture the loop variables, as evidenced by the fact that none of the languages that have started like this and taken a breaking change to switch to the expected behavior has found even a single bug caused by this breaking change.False. There are cases where it is useful to have the loop variable available directly. For example, you can add one to the loop variable to skip an iteration, which would not work with an iteration-local loop variable.
I do agree that there are reasons to modify the iteration variable in a C-style for loop, so I am surprised that those loops are being modified as well. C#, which went through a similar change, did NOT apply such a change for those for loops.