Primož Gabrijelčič 223 Posted November 20, 2018 (edited) Today I was porting some legacy code and noticed a weird warning: Weird warning, I thought. Obviously the loop variable can be passed as a var parameter as the code compiles. Why a warning and not an error, then? Stefan later reminded me that warnings are quite extensively covered in the documentation. Indeed, the docs for W1015 clearly state: This is a warning because the procedure which receives the control variable is able to modify it, thereby changing the semantics of the FOR-loop which issued the call. Ah! So they actually want to say “… FOR-Loop variable ‘i’ should not be …” Furthermore, this brings several interesting possibilities to the table. For example, we can finally write for..to..step loops in Delphi! program ForInStep; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; procedure Step(var loop: integer; step: integer); begin loop := loop + step - 1; end; var i: integer; begin for i := 1 to 10 do begin Writeln(i); Step(i, 2); end; Readln; end. Indeed, this works! Danger, Will Robinson! To be clear, I do not recommend writing such code. If you get warning W1015, you should rewrite the code. Such code is hard to maintain and will eventually cause you big problems. I certainly did fix the original code although the method that was called from the for loop did not modify its var parameter. (It was declared var and not const because the method was written before const parameters were introduced to Delphi. Oh, the joys of legacy code!) Edited November 20, 2018 by Primož Gabrijelčič 1 1 Share this post Link to post
FredS 138 Posted November 21, 2018 7 hours ago, Primož Gabrijelčič said: For example, we can finally write for..to..step loops in Delphi! From Marco: Stepping through Values in a Delphi for Loop There has been a recent request to add to for loop in the Object Pascal language the ability to add a step, that is an increment different from one. Here is an alternative implementation using a for..in loop and a custom enumerator. 1 Share this post Link to post
Arnaud Bouchez 407 Posted November 21, 2018 (edited) Just use a while loop! This is the clear and efficient way of writing such code, without messing with the "for" variable. var i: integer; begin i := 1; while i <= 10 do begin Writeln(i); inc(i, 2); end; Readln; end. Or a repeat .. until of course: var i: integer; begin i := 1; repeat Writeln(i); inc(i, 2); until i > 10; Readln; end. Edited November 21, 2018 by Arnaud Bouchez 1 Share this post Link to post
Primož Gabrijelčič 223 Posted November 21, 2018 Of course, Andre. There are many ways of doing this in a clean manner. You can also write a nice iterator and call for i in Range(1, 10, 2) do That was not a point of the post, though. I wanted to point out one shady part of our bellowed language. Share this post Link to post
Kryvich 165 Posted November 21, 2018 We can always "fool" the compiler using pointers. procedure TestMyStep; var i: Integer; begin for i := 1 to 10 {step 2} do begin Writeln(i); PInteger(@i)^ := PInteger(@i)^ + 2-1; end; Readln; end; But we must understand what we are doing. The compiler checks the counter for an exact match with the final value. If in your (or my) example the step size would be 3, we would get an infinite loop. Share this post Link to post
Primož Gabrijelčič 223 Posted November 21, 2018 30 minutes ago, Kryvich said: If in your (or my) example the step size would be 3, we would get an infinite loop. As I have said: Quote Such code ... will eventually cause you big problems. Share this post Link to post
Lars Fosdal 1793 Posted November 21, 2018 Not efficient, not tested, but fun 😄 User exercise - make a Range.AsDouble(0, 5, 0.25) type Range = record class function AsInt(const aFrom, aTo: Integer; const aStep: Integer = 1): TArray<Integer>; static; procedure Test; end; class function Range.AsInt(const aFrom, aTo, aStep: Integer): TArray<Integer>; var ix, n: Integer; v: Integer; begin n := ((aTo - aFrom) + 1) div aStep; SetLength(Result, n + 1); v := aFrom; ix := 0; while v <= aTo do begin Result[ix] := v; v := v + aStep; end; SetLength(Result, ix + 1); end; procedure Range.Test; var ix: Integer; begin for ix in Range.AsInt(0, 11, 2) do begin Writeln(ix); end; end; 1 Share this post Link to post
Arnaud Bouchez 407 Posted November 21, 2018 Perhaps fun for you, but certainly complicated and slower than a simple "while" loop, especially when the process within the loop is small and the range is huge. The duplicated SetLength() is just so "funny". Share this post Link to post
Lars Fosdal 1793 Posted November 21, 2018 1 hour ago, Arnaud Bouchez said: Perhaps fun for you, but certainly complicated and slower than a simple "while" loop, especially when the process within the loop is small and the range is huge. The duplicated SetLength() is just so "funny". The duplicate SetLength was a lazy way of avoiding the corner case math check. It may occasionally reduce the length of an array, which afaik is not that costly? Some points for doing it this way: - it is a reusable pattern without embedded logic in the loop, which gives readable code - we could do it the same way for a TArray<double> with fractional fractional increments - a similar pattern can be used to fetch pre-calculated arrays There are so many ways to use Delphi Share this post Link to post
Arnaud Bouchez 407 Posted November 21, 2018 Range.AsInt(0, 11, 2) is IMHO less readable than a regular while loop. Why not Range.AsInt(0, 2, 11) or Range.AsInt(2, 0, 11) ? Someone not able to read and understand the following loop would have to learn it ASAP before coding in Delphi any further: var i: integer; begin i := 1; repeat Writeln(i); inc(i, 2); until i > 10; end. Share this post Link to post
Stefan Glienke 2019 Posted November 21, 2018 (edited) @Arnaud Bouchez I know you like to deal with low level stuff but being able to be more declarative about loops and other language constructs is a benefit imo. That var passing of the loop variable is bad, but Primoz wrote that just for fun and advised not to use it. I guess every Delphi developer knows how a while or repeat loop works but I have seen enough off by one errors caused by counting the loop variable wrong, messing up the loop condition or other stuff which could just be eliminated if the language itself could treat it right without the need to put extra abstractions ontop that just make code slower. Another thing that just applies to 10.3 though is that only in a for loop you can inline declare the variable to only be valid in the loop scope and not outside. Edited November 21, 2018 by Stefan Glienke 3 Share this post Link to post