Recent

Author Topic: For-in loop over constant array of strings - limited to length of first string?  (Read 5150 times)

TRon

  • Hero Member
  • *****
  • Posts: 3643
the ability to manipulate an array of constants, i.e. the number of its elements (adding, deleting) and its contents (changing the value of a specific cell) is a definite disadvantage. An array of constants should not be modifiable. Otherwise it's basically no different from a regular dynamic array.
If that is a requirement then it is not possible to use a typed const as that is known/documented behaviour.
This tagline is powered by AI (AI advertisement: Free Pascal the only programming language that matters)

Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1226
Using for in syntax for an array seems unnecessary and probably should not have been implemented. It makes sense for a set where items are in no particular order . I wonder why they added this bloated feature? Is it for delphi compatibility or something?
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

VisualLab

  • Hero Member
  • *****
  • Posts: 575
the ability to manipulate an array of constants, i.e. the number of its elements (adding, deleting) and its contents (changing the value of a specific cell) is a definite disadvantage. An array of constants should not be modifiable. Otherwise it's basically no different from a regular dynamic array.
If that is a requirement then it is not possible to use a typed const as that is known/documented behaviour.

Yes, I know that.

And I agree that:

Quote
Sometimes it is necessary to specify the type of a constant, for instance for constants of complex structures (defined later in the manual).

But I believe this:

Quote
Contrary to ordinary constants, a value can be assigned to them at run-time. This is an old concept from Turbo Pascal, which has been replaced with support for initialized variables.

 should only be possible in TurboPascal mode. In modes Fpc and ObjFpc should not be supported. Or this:

Quote
Support for assigning values to typed constants is controlled by the {$J} directive: it can be switched off, but is on by default (for Turbo Pascal compatibility). Initialized variables are always allowed.

in Fpc and ObjFpc modes it should be disabled by default.


Thaddy

  • Hero Member
  • *****
  • Posts: 16193
  • Censorship about opinions does not belong here.
If I smell bad code it usually is bad code and that includes my own code.

TRon

  • Hero Member
  • *****
  • Posts: 3643
Yes, I know that.
In that case sorry for being a redundant remark. I wasn't sure whether or not you did,

Quote
And I agree that:

But I believe this:

in Fpc and ObjFpc modes it should be disabled by default.
The way these kind of things work is usually a mystery to me. Might even be debatable what is the 'correct' way to do it but experience in the past have shown there always seem to be a (valid) reason for having chosen the approach as (currently) implemented.
This tagline is powered by AI (AI advertisement: Free Pascal the only programming language that matters)

VisualLab

  • Hero Member
  • *****
  • Posts: 575
Using for in syntax for an array seems unnecessary and probably should not have been implemented. It makes sense for a set where items are in no particular order . I wonder why they added this bloated feature? Is it for delphi compatibility or something?

No, it's not unnecessary. This is absolutely useful. And it's good that it was implemented. I agree with what CCRDude wrote:

Is there any good reason to use for.. in to iterate through an array instead of using a for loop with index? I’m only familiar with using for.. in in the context of things like sets where the number of items is unknown. I’ve also seen it used for fields in an sql dataset.

Abstraction of iteration?
Prevent errors through wrong index, especially with nested loops?
Less defined local variables (the index) improves readability of function head?
Readability in the iteration itself?

Code: [Select]
for i := 0 to Pred(Length(A)) do begin
   item := A[i];
   item.DoSomething;
end;

for i := 0 to Pred(Length(A)) do begin
   A[i].DoSomething;
end;

for item in A do begin
   item.DoSomething;
end;

This is useful not only for sets and arrays, but also for strings, lists, collections, and similar iterable objects.

VisualLab

  • Hero Member
  • *****
  • Posts: 575

VisualLab

  • Hero Member
  • *****
  • Posts: 575
Yes, I know that.
In that case sorry for being a redundant remark. I wasn't sure whether or not you did,

There is no reason to apologize. Everything is OK. This information can be used by someone else who doesn't know it and is browsing the forum.

But I believe this:

in Fpc and ObjFpc modes it should be disabled by default.
The way these kind of things work is usually a mystery to me. Might even be debatable what is the 'correct' way to do it but experience in the past have shown there always seem to be a (valid) reason for having chosen the approach as (currently) implemented.

Indeed, it may be so. Sometimes, after a longer period of time, certain shortcomings of the solution used may become apparent.

alpine

  • Hero Member
  • *****
  • Posts: 1302
@VisualLab, CCRDude
Despite I like the enumerators too, they are IMHO just a syntax sugar and much underdeveloped.

Consider the following loop:
Code: Pascal  [Select][+][-]
  1.   for I := Pred(C.Count) downto 0 do
  2.     if SomeCond(C[I]) then
  3.       C.Delete(i);

"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

PascalDragon

  • Hero Member
  • *****
  • Posts: 5759
  • Compiler Developer
Using for in syntax for an array seems unnecessary and probably should not have been implemented. It makes sense for a set where items are in no particular order . I wonder why they added this bloated feature? Is it for delphi compatibility or something?

Yes, it was added due to Delphi-compatibility, but it's more than just iterating over arrays: you can also iterate through sets and strings, but with the use of enumerators you can also iterate inside classes or records. For example you can iterate through the strings in a TStringList or the objects in a TObjectList. Or you can create your own enumerators. It's a relatively powerful feature.

But I believe this:

Quote
Contrary to ordinary constants, a value can be assigned to them at run-time. This is an old concept from Turbo Pascal, which has been replaced with support for initialized variables.

 should only be possible in TurboPascal mode. In modes Fpc and ObjFpc should not be supported. Or this:

Quote
Support for assigning values to typed constants is controlled by the {$J} directive: it can be switched off, but is on by default (for Turbo Pascal compatibility). Initialized variables are always allowed.

in Fpc and ObjFpc modes it should be disabled by default.

The FPC and ObjFPC modes derive from the TP and Delphi modes and both had by default writable constants enabled, thus the FPC-specific modes had as well and nowadays this counts as backwards compatibility, so this will not change.

VisualLab

  • Hero Member
  • *****
  • Posts: 575
But I believe this:

Quote
Contrary to ordinary constants, a value can be assigned to them at run-time. This is an old concept from Turbo Pascal, which has been replaced with support for initialized variables.

 should only be possible in TurboPascal mode. In modes Fpc and ObjFpc should not be supported. Or this:

Quote
Support for assigning values to typed constants is controlled by the {$J} directive: it can be switched off, but is on by default (for Turbo Pascal compatibility). Initialized variables are always allowed.

in Fpc and ObjFpc modes it should be disabled by default.

The FPC and ObjFPC modes derive from the TP and Delphi modes and both had by default writable constants enabled, thus the FPC-specific modes had as well and nowadays this counts as backwards compatibility, so this will not change.

This was to be expected, i.e. a continuation of "historical events" due to the amount of existing code. Honestly, I've never had the need to use writable constants. But I also never turned off the option in the code. This means I have to remember to add appropriate compiler directives to the modules of my own programs and libraries. Well, that's what programming is all about, you have to remember a lot of different little things :-)

Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1226
Ok, I get the idea that for in is a simplistic way to iterate through things however as alpine mentioned, sometimes it’s desirable to iterate in descending order. You can’t use “for ... in” to iterate backwards can you?
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

VisualLab

  • Hero Member
  • *****
  • Posts: 575
Ok, I get the idea that for in is a simplistic way to iterate through things however as alpine mentioned, sometimes it’s desirable to iterate in descending order. You can’t use “for ... in” to iterate backwards can you?

Yes, you can't iterate backwards. Additionally, sometimes you need a loop counter value for use in statements inside that loop. Then you also cannot use this version of the "for" loop. It's, as Alpine mentioned, syntactic sugar, but it's convenient in certain situations.

Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1226
I often use the for loop index for naming  controls and case statements.
« Last Edit: April 18, 2024, 11:28:24 pm by Joanna »
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

PascalDragon

  • Hero Member
  • *****
  • Posts: 5759
  • Compiler Developer
Ok, I get the idea that for in is a simplistic way to iterate through things however as alpine mentioned, sometimes it’s desirable to iterate in descending order. You can’t use “for ... in” to iterate backwards can you?

If one provides a suitable enumerator one can:

Code: Pascal  [Select][+][-]
  1. program tenumerator;
  2.  
  3. {$mode objfpc}
  4. {$modeswitch advancedrecords}
  5. {$modeswitch implicitfunctionspecialization}
  6.  
  7. uses
  8.   Classes;
  9.  
  10. {$Region type with enumerator}
  11. type
  12.   TInverseStringsEnumerator = class
  13.   private
  14.     FStrings: TStrings;
  15.     FPosition: Integer;
  16.   public
  17.     constructor Create(AStrings: TStrings);
  18.     function GetCurrent: String;
  19.     function MoveNext: Boolean;
  20.     property Current: String read GetCurrent;
  21.   end;
  22.  
  23.   TMyStrings = class(TStringList)
  24.     function GetInverseEnumerator: TInverseStringsEnumerator;
  25.   end;
  26.  
  27. constructor TInverseStringsEnumerator.Create(AStrings: TStrings);
  28. begin
  29.   inherited Create;
  30.   FStrings := AStrings;
  31.   FPosition := AStrings.Count;
  32. end;
  33.  
  34. function TInverseStringsEnumerator.GetCurrent: String;
  35. begin
  36.   Result := FStrings[FPosition];
  37. end;
  38.  
  39. function TInverseStringsEnumerator.MoveNext: Boolean;
  40. begin
  41.   Dec(FPosition);
  42.   Result := FPosition >= 0;
  43. end;
  44.  
  45. function TMyStrings.GetInverseEnumerator: TInverseStringsEnumerator;
  46. begin
  47.   Result := TInverseStringsEnumerator.Create(Self);
  48. end;
  49. {$EndRegion}
  50.  
  51. {$Region enumerator utility}
  52. type
  53.   generic TEnumeratorClassWrapper<T> = class
  54.   private
  55.     fEnum: T;
  56.   public
  57.     constructor Create(aEnum: T);
  58.     destructor Destroy; override;
  59.     function GetEnumerator: T;
  60.   end;
  61.  
  62. constructor TEnumeratorClassWrapper.Create(aEnum: T);
  63. begin
  64.   fEnum := aEnum;
  65. end;
  66.  
  67. destructor TEnumeratorClassWrapper.Destroy;
  68. begin
  69.   fEnum.Free;
  70.   inherited;
  71. end;
  72.  
  73. function TEnumeratorClassWrapper.GetEnumerator: T;
  74. begin
  75.   Result := fEnum;
  76. end;
  77.  
  78. generic function WrapClass<T>(aEnum: T): specialize TEnumeratorClassWrapper<T>;
  79. begin
  80.   Result := specialize TEnumeratorClassWrapper<T>.Create(aEnum);
  81. end;
  82.  
  83. type
  84.   generic TEnumeratorRecordWrapper<T> = record
  85.   private
  86.     fEnum: T;
  87.   public
  88.     constructor Create(aEnum: T);
  89.     function GetEnumerator: T;
  90.   end;
  91.  
  92. constructor TEnumeratorRecordWrapper.Create(aEnum: T);
  93. begin
  94.   fEnum := aEnum;
  95. end;
  96.  
  97. function TEnumeratorRecordWrapper.GetEnumerator: T;
  98. begin
  99.   Result := fEnum;
  100. end;
  101.  
  102. generic function WrapRecord<T>(aEnum: T): specialize TEnumeratorRecordWrapper<T>;
  103. begin
  104.   Result := specialize TEnumeratorRecordWrapper<T>.Create(aEnum);
  105. end;
  106. {$EndRegion}
  107.  
  108. var
  109.   slist: TMyStrings;
  110.   s: String;
  111. begin
  112.   slist := TMyStrings.Create;
  113.   try
  114.     slist.Add('One');
  115.     slist.Add('Two');
  116.     slist.Add('Three');
  117.  
  118.     Writeln('In order:');
  119.     for s in slist do
  120.       Writeln(s);
  121.  
  122.     Writeln;
  123.     Writeln('Reverse:');
  124.     for s in WrapClass(slist.GetInverseEnumerator) do
  125.       Writeln(s);
  126.   finally
  127.     slist.Free;
  128.   end;
  129. end.

The assumption is that TMyStrings is a type that provides both an ordinary enumerator (provided by TStrings.GetEnumerator) and a reverse iterator (provided by TMyStrings.GetInverseEnumerator). Due to how enumerators work (the compiler looks for a GetEnumerator method in the type or for a suitable Enumerator operator overload) I've used a generic wrapper function utilizing implicit function specializations (thus WrapClass() instead of specialize WrapClass<TInverseStringEnumerator>()) that can then be used to access any enumerator (one only needs to differentiate between whether the enumerator is a class or a record/interface).

So it's relatively few code that needs to be written every time.

 

TinyPortal © 2005-2018