I think the speciality of the 'for' iterator is quite obvious. A 'for..to' loop always has exactly one iterator. The iterator variable of the loop is read/write outside the loop, but readonly within the loop.
This is probably out of the need of an implementation detail, which allows the compiler to optimize the loop behaviour, but nevertheless.
[...]
That is the "for" loop can NOT declare a new variable
You take a different conclusion of my argument than me. What I mean is: Because the iterator variable is that special, directly tied to the loop and in almost all cases of local integer type. Because of this it feels a waste of time to do a declaration (of something obvious). So the aim is to make things less complicated (by omitting the decleration) and not to make them more complicated by additionally forced flags like "iterator".
And thats the answer to the "why": To reduce efforts. Avoid that the programmers has to consider things that the compiler can do itself.
So now it is NOT about the uninitialized behaviour of the iterator after the loop. But it is about "saving to type a few extra chars". Is that correct?
Actually, I have plenty of loops that iterate over other enum types. (Search the IDE for "procedure dbgs" / typical pattern: for i := low(enumtype) to high(...)...)
But even if it was mostly integer. The declaration of any variable on top, tells me more than the type it has. It tells me the name is used as an identifier.
As I wrote before in this thread, if the loop iterator would not be declared in the var block, and I wanted to add a variable (for none loop purpose), then I would have to search the entire procedure body for any "for" loops, so I know the name is not used. That is a massive amount of extra work.
So in order to know about all the identifier names that are already used in the procedure, the iterator too must be listed in the var block. (never mind in which way, but it must be there)
Further more
for i := 0 to foo.count -1
foo.count may be cardinal. Mixed signed expression may give a 64 bit result. But I most likely do not want "i" to be int64. So I do need to specify it.
Or I may want I to be a smaller type, because it will never exceed it, and a lot of operations in the loop depend on it.
Of course, I understand, if I need a type different from the declared type, then I can still set it. But that is not all of it. "foo.count" may change from integer to cardinal, after I wrote the loop.
If there even is
as much as the possibility that I have somewhere a loop that uses the derived type for the iterator, rather than a declared type, then I will have to search my entire code base (and that really is a lot of work, especially if the change is in a package on which many projects depend).
So for that reason I need the compiler to enforce that the type of the iterator is always declared.And finally (and on that your opinion may differ), I prefer it that if foo.count changes to an incompatible type, then the compiler will not compile the loop.
So I can check it, if changing the declared type will work as I expect it, or if it will have side effects. (assuming the loop body compiles with the new iterator type, otherwise the derived iterator will give an error too)
Besides:
To reduce efforts.
If you do not want to give it much attention, how much effort is it to press Ctrl-Shift-C (you can even assign it to something easier)?
Codetool will do it for you, if you ask it.
<half sarcasm>
If you want to save keystrokes (I understand the above may also be, saving to think about what type will be needed). You can add a code template, so that typing
"{ " will expand to "begin ". Saves 4 keystrokes (and 2 in case of end)
</half sarcasm>
Yes I know it is optional, but if there are cases where it can not be used at all (not even optional), then that means that even if you want the feature you are left with some code that even you must write in the old way, and even you still will not get the "protection" you seek.
So the new feature would be incomplete.
I don't seek any protection, but simplycity The arguments about the uninitialised variable in my point of view is not about that protection after the loop is needed, but that the other way round there is no reason why the loop variable after the loop still should be accessible at all.
Equally I could say there is no reason why it should not be available (Especially after you wrote, you don't seek protection from accidentally accessing it).
It can even be convenient to have the variable for other purposes (as long as you initialize it). IMHO having it around increases simplicity. Otherwise I may need additional other vars for use after the loop. More vars needed => less simplicity.
To be honest, if I had the power I wouldn't want to have to decide it. There are pros and cons. Both imo strong ones.
Well yes, people have given actual arguments for both sides. IMHO the pro were not very convincing, but that is based on personal bias.
There are quite a few people (if not the majority), who have not gone beyond opinion. I have to assume (and I would understand that), that they come from a language in which it works the way the want it.
It can be hard to go from one language to another, you are used to structure your problems and code according to the features of the language you used. (been there done that).
"being used to it" is no argument.
Neither is "c or js have it" an argument. Then I could say in some languages I do not need to declare vars at all.... (they all get derived from context, and casted if needed)
One of the principals of pascal is that you give a type to each variable (that includes iterators).
Well yes it is broken already, there is an untyped parameter. But at least you still have to explicitly typecast it, to use it. (Not something you want to do with the iterator).
- Why do we want special/extra protection, if that uninitialized var was a "for" iterator before?
- IMHO, *ALL* uninitialized deserve the same lever of attention/protection.
- An application certainly will be same as buggy if it hits an uninitialized var never mind if this was a "for" iterator, or otherwise uninitialized.
So again: What makes the uninitialized var after a loop so different to other uninitialized var?
In contrast to the documentation the practice shows the bahaviour that the variable is initialized to the last loop's value (At least in cases). Also this is a common feature of many programming languages.
program Project1;{$mode objfpc}{$H+}
var i: integer;
begin
for i := 2 to 9 do writeln();
writeln(i);
readln;
end.
Compile with -O4
I get "0".
BUT: I wouldn't add protection because of that. Rather I'd make it a documented feature.
Ok, so that would be a different new feature.
If it where to be implemented, no objections from me.
But I would severely hope the optimizer in fpc would be enhanced for enough to detect, if the iterator is needed after the loop. So in cases were it is not needed, the loop can still be optimized. Would be a shame to loose clock cycles in all loops.
What is actually assigned to "i", and when?
I read it like that (for i:= 1 to 3): "loopwise assign values for i starting with 1 ending with 3", i.e.:
Well we all got used to read it like this.
But none the less, the 3 statement you made out of it (which each have an RHS evaluating to a single value of the correct type) are not part of the written source code.
And the actual present ":=" does not have a RHS that returns a value of the type.
I agree that the := in the "for" statement misleads to the assumption that there will be the assignments you stated. But that is:
it "misleads". So if that is the problem, then lets fix it.
And to be precise the proposed (with or without the ":integer" part)
for var I: integer := 1 to 3 do begin end;
would mislead as well.
The "var i" is not within the "begin/end" scope. So why would I assume it to be limited to that scope. I would of course assume, that it will be scoped to the end of the procedure (or maybe enclosing begin/end, if there was another).
Same as I would do in this case:
if a= b then begin
foo();
var c: integer; // statement declaring c, ends at the ";"
if bar() then begin
c:= 1;
end;
c := 2;
end; // scope for c ends here
It may be different in c/js, but why would I even think about rules of a language that is not the one I currently write code in?