Jump to content

Recommended Posts

Today I was porting some legacy code and noticed a weird warning:

 

image.png.4a79d0b3afd6b196e484ca2eb9fc6856.png

 

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!

 

image.png.239ea42e73346e0edba03f29d84e91da.png

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 by Primož Gabrijelčič
  • Like 1
  • Thanks 1

Share this post


Link to post
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.

  • Like 1

Share this post


Link to post

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 by Arnaud Bouchez
  • Like 1

Share this post


Link to post

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

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
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

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;

 

  • Like 1

Share this post


Link to post

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
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

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

@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 by Stefan Glienke
  • Like 3

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×