Lazarus

Programming => General => Topic started by: Thaddy on July 11, 2025, 11:00:20 am

Title: Circumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 11, 2025, 11:00:20 am
This is more like a tip:
I was reviewing some code this morning by someone who was amazed his code did not work anymore.
It is the problem that the compiler has changed behaviour regards assigning member values of a record (or class for that matter) inside a for in do loop.
Well, he was right and wrong. His code used to work, but there is an easy work-around that I will add to the wiki:
Code: Pascal  [Select][+][-]
  1. program ranges;
  2. uses
  3.   sysutils;
  4. type
  5.   range = 1..5;
  6.   Tmyrecord = record
  7.     name:string;
  8.   end;
  9. var
  10.   records: array[range] of TMyRecord;
  11.   num: Integer;
  12.   rec: TMyRecord;
  13.  begin
  14. {$region 'affected code'}
  15.  { this is illegal, because rec as an index can not be modified, not even its members. (this used to be possible, but is removed).
  16.    iow rec is immutable. }  
  17. {$if 0 this way highlighting stays..}
  18.    for rec in records do
  19.     rec.name := 'test';
  20. {$ifend}
  21. {$endregion}
  22.  // but this is..
  23.   for num in range do
  24.     records[num].name := num.tostring;
  25.  // and this too..
  26.   for rec in records do
  27.     writeln('record ',rec.name);
  28.   readln;
  29. end.

This means you still have the advantage of for in do, thus ensuring correct range at all times, but does allow the assignment.

I have seen that problem before, also on the forum, but this is a correct solution.
The importance is just that it keeps the range intact at all times and you can likely not make indexing mistakes.
Btw: this is not delphi compatible, because delphi does not allow enumerating over a type.
The screenshot has an overview made by deepseek (Eat your hart out Joanna).
*"Source: DeepSeek-R1, an AI assistant by DeepSeek"*
License for  the picture is:
 Creative Commons CC0

The code is mine, so as usual that is without a license, with the provision you do not claim it as your own.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: gues1 on July 12, 2025, 09:58:49 am
Why would anyone use this solution instead of the classic alternatives (for to do, while), which are much more readable and maintainable?
Wherever there's a "collection," there are certainly methods to access the list, so a while (where "for to do" can't be used due to the lack of an index) is clearer.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: korba812 on July 12, 2025, 11:50:59 am
If I understand correctly in the "for rec in records" loop, rec variable contains a copy of array element, not a reference to element. So modifying fields of such a record makes no sense because changes will not be propagated to array element.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 12, 2025, 12:55:12 pm
Why would anyone use this solution instead of the classic alternatives (for to do, while), which are much more readable and maintainable?
Wherever there's a "collection," there are certainly methods to access the list, so a while (where "for to do" can't be used due to the lack of an index) is clearer.
I am of the complete opposite opinion:
For to do  is very prone to indexing mistakes and index overflows. Ranges make that impossible. It protects the user from indexing mistakes and the syntax ios more readable in the sense that for in do is way more concise and expresses that each element in the collection needs processing.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 12, 2025, 01:07:50 pm
If I understand correctly in the "for rec in records" loop, rec variable contains a copy of array element, not a reference to element. So modifying fields of such a record makes no sense because changes will not be propagated to array element.
You are mistaken in the sense that there are two separate for in do's .
- iterate over  elements where the elements can not be modified. E.g. a record in an array of records can be read but not be modified: for rec in records do
- iterate over an ordinal range representing the index, not the element but the index to the element in the array of elements. That is different.
And what this does:
Code: Pascal  [Select][+][-]
  1. type
  2.  
  3.   range = 1..5;
  4.  
  5.   Tmyrecord = record
  6.     name:string;
  7.   end;
  8.  
  9. var
  10.   records: array[range] of TMyRecord;
  11.   num: Integer;
  12.   rec: TMyRecord;
  13.  begin
  14.    { second incarnation is read/write}
  15.   for num in range do
  16.     writestr(records[num].name, num);
  17.   readln;
  18.  { first incarnation is read only}
  19.   for rec in records do
  20.     writeln('record ',rec.name);
  21.  { first incarnation }
  22.   for rec in records do
  23.     writeln('record ',rec.name);
  24. end.
This makes for completely safe indexing: every index operation has the same range.
It is also notationally shorted and better understandable.
It also works for multi-dimensional arrays.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Martin_fr on July 12, 2025, 01:11:56 pm
As for error prone. Each solution has its pro and con

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. type
  3.   TFoo = (f1,f2,f3);
  4.   TBar = set of TFoo;
  5. var
  6.   i: TFoo;
  7.   ABar: TBar;
  8. begin
  9.   ABar := [f1,f3];
  10.   write('TBar: '); for i in TBar do write(i, ' '); writeln;
  11.   write('ABar: '); for i in ABar do write(i, ' '); writeln;
  12.   readln;
  13. end.
  14.  


TBar or ABar => tiny difference. The compiler will happily take both. But the result is completely different.

Also, the "ABar" loop is not just a different syntax, but a new functionality (saves the "if" condition). Of course it requires the reader to
- know
- check exactly if its on the type or var



But the "in" syntax allows for more... If the enumerator is done well. Then e.g. it may allow for loops on lists that have elements removed/inserted during the loop. Adapting its internal index to still be at the correct element. Of course, that isn't guaranteed by just using that syntax. That is only true, if the enumerator is correctly written for this use case. But the syntax allows to write such an enumerator.

Then again, its just a smart way to hide away the function that does the enumeration....
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 12, 2025, 01:29:52 pm
First is a non-argument.Same as TFoo and IFoo.
Also it is not just a smart way to hide something: it prevents you from making mistakes with that something.
So "just" doesn't do justice (pun intended). This is about defensive and safe programming.

And yes, apart from being less error prone, for in do has more and more flexible application than for x :=1 to y do has.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: WooBean on July 12, 2025, 08:47:44 pm
Cirumvent  or Circumvent?
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: jamie on July 13, 2025, 01:06:31 am
Cirumvent  or Circumvent?

This aint no spelling BEEEEEE you know, Ayah!

Jamie
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: dbannon on July 13, 2025, 03:09:01 am
Would I be right in saying that a "for in" that involves each iteration making a copy of the whole record just to access one field of it would have to be slower that indexing an integer ? Remembering that record may well contain a lot of fields ...

Or do we argue that is all optimized  away ?

Davo
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 13, 2025, 05:17:20 am
There is no copying. The write part is over the index. NOT the entity, You don't want to see that?
It is internally nothing more than for x := low(range) to high(range) do
Better still the generated code is exactly the same. Which you should have looked at and then you would have known that.
Code: ASM  [Select][+][-]
  1. # Var i located in register r8d
  2. # [13] i := 0;
  3.         xorl    %r8d,%r8d
  4.         leaq    U_$P$PROGRAM_$$_ARR(%rip),%rax
  5. # Var x located in register r9d
  6. # [14] for x in range do
  7.         movl    $5,%r9d
  8.         .p2align 4,,10
  9.         .p2align 3
  10. .Lj3:
  11. # [15] for y in range do
  12.         movl    $1,%edx
  13.         .p2align 4,,10
  14.         .p2align 3
  15. .Lj6:
  16. # [18] inc(i);
  17.         addl    $1,%r8d
  18. # [19] arr[x,y,z] := i;
  19.         movl    %edx,%ecx
  20.         leaq    (%rcx,%rcx,4),%rcx
  21.         shlq    $2,%rcx
  22.         movl    %r8d,-20(%rax,%rcx)
  23.         addl    $1,%r8d
  24.         movl    %edx,%ecx
  25.         leaq    (%rcx,%rcx,4),%rcx
  26.         shlq    $2,%rcx
  27.         movl    %r8d,-16(%rax,%rcx)
  28.         addl    $1,%r8d
  29.         movl    %edx,%ecx
  30.         leaq    (%rcx,%rcx,4),%rcx
  31.         shlq    $2,%rcx
  32.         movl    %r8d,-12(%rax,%rcx)
  33.         addl    $1,%r8d
  34.         movl    %edx,%ecx
  35.         leaq    (%rcx,%rcx,4),%rcx
  36.         shlq    $2,%rcx
  37.         movl    %r8d,-8(%rax,%rcx)
  38.         addl    $1,%r8d
  39.         movl    %edx,%ecx
  40.         leaq    (%rcx,%rcx,4),%rcx
  41.         shlq    $2,%rcx
  42.         movl    %r8d,-4(%rax,%rcx)
  43.         addl    $1,%edx
  44.         cmpl    $5,%edx
  45.         jng     .Lj6
  46.         addq    $100,%rax
  47.         subl    $1,%r9d
  48.         jne     .Lj3
  49. # Var i located in register r8d
  50. # [24] i := 0;
  51.         xorl    %r8d,%r8d
  52.         leaq    U_$P$PROGRAM_$$_ARR(%rip),%rax
  53. # Var x located in register r9d
  54. # [25] for x := 1 to 5 do
  55.         movl    $5,%r9d
  56.         .p2align 4,,10
  57.         .p2align 3
  58. .Lj9:
  59. # [26] for y := 1 to 5 do
  60.         movl    $1,%edx
  61.         .p2align 4,,10
  62.         .p2align 3
  63. .Lj12:
  64. # [29] inc(i);
  65.         addl    $1,%r8d
  66. # [30] arr[x,y,z] := i;
  67.         movl    %edx,%ecx
  68.         leaq    (%rcx,%rcx,4),%rcx
  69.         shlq    $2,%rcx
  70.         movl    %r8d,-20(%rax,%rcx)
  71.         addl    $1,%r8d
  72.         movl    %edx,%ecx
  73.         leaq    (%rcx,%rcx,4),%rcx
  74.         shlq    $2,%rcx
  75.         movl    %r8d,-16(%rax,%rcx)
  76.         addl    $1,%r8d
  77.         movl    %edx,%ecx
  78.         leaq    (%rcx,%rcx,4),%rcx
  79.         shlq    $2,%rcx
  80.         movl    %r8d,-12(%rax,%rcx)
  81.         addl    $1,%r8d
  82.         movl    %edx,%ecx
  83.         leaq    (%rcx,%rcx,4),%rcx
  84.         shlq    $2,%rcx
  85.         movl    %r8d,-8(%rax,%rcx)
  86.         addl    $1,%r8d
  87.         movl    %edx,%ecx
  88.         leaq    (%rcx,%rcx,4),%rcx
  89.         shlq    $2,%rcx
  90.         movl    %r8d,-4(%rax,%rcx)
  91.         addl    $1,%edx
  92.         cmpl    $5,%edx
  93.         jng     .Lj12
  94.         addq    $100,%rax
  95.         subl    $1,%r9d
  96.         jne     .Lj9
  97. # [34] end.
  98.  
Title: Re: Circumvent the assignment problem in for in do constructs.
Post by: bytebites on July 13, 2025, 08:41:02 am
There is fpc_copy function call when iterating by record??
Code: Pascal  [Select][+][-]
  1. /home/norsu/tmp/project1.lpr:18           for rec in records do
  2. 00000000004010CE 30DB                     xor bl,bl
  3. 00000000004010D0 80C301                   add bl,$01
  4. 00000000004010D3 488D05D6020A00           lea rax,[rip+$000A02D6]    # $00000000004A13B0
  5. 00000000004010DA 48894598                 mov [rbp-$68],rax
  6. 00000000004010DE 0FB6C3                   movzx eax,bl
  7. 00000000004010E1 488D1588020A00           lea rdx,[rip+$000A0288]    # $00000000004A1370
  8. 00000000004010E8 488D44C2F8               lea rax,[rax*8+rdx-$08]
  9. 00000000004010ED 48894590                 mov [rbp-$70],rax
  10. 00000000004010F1 488D1580C00700           lea rdx,[rip+$0007C080]    # $000000000047D178
  11. 00000000004010F8 488B7598                 mov rsi,[rbp-$68]
  12. 00000000004010FC 488B7D90                 mov rdi,[rbp-$70]
  13. 0000000000401100 E89B780100               call +$0001789B    # $00000000004189A0 FPC_COPY
  14.  
Title: Re: Circumvent the assignment problem in for in do constructs.
Post by: PascalDragon on July 13, 2025, 07:17:35 pm
There is fpc_copy function call when iterating by record??

Yes, because an enumerator for a forin-loop returns a copy.
Title: Re: Circumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 13, 2025, 08:31:45 pm
I explained the distinction.very clear iterating over a range type does not make a copy. I
 See the generated assembler above :Identical with for x:= y to z do.
iterating with a member over a collection does make a copy.
Title: Re: Cirumvent the assignment problem in for in do constructs.
Post by: Thaddy on July 17, 2025, 06:43:32 pm
Cirumvent  or Circumvent?
I forgot the typo. Fixed. Tnx.
TinyPortal © 2005-2018