Sign in Join
Wiki copytemporary

for-in loop: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: for-in loop

OldNewLine
1+This iterates over a collection, but where a [[For|basic for-loop]] uses a numerical index counter, a for-in loop instead retrieves collection elements into the counter variable for immediate use. For-in works on strings, arrays, sets, and any other custom collection that implements the required iterators. Looping over an empty collection does nothing. The counter variable can not be modified inside the loop.
2+
3+The **for-in loop** construct is supported in Delphi from Delphi 2005 onwards. It was implemented in FPC 2.4.2.
4+
5+The official documentation is here: [Reference guide chapter 13](https://www.freepascal.org/docs-html/ref/refsu58.html)
6+
7+## Delphi and FPC implementation
8+
9+A **for in** loop has the following syntax:
10+
11+### String loop
12+
13+``` pascal
14+procedure StringLoop(S: String);
15+var
16+ C: Char;
17+begin
18+ for C in S do
19+ DoSomething(C);
20+end;
21+```
22+
23+### Array loop
24+
25+``` pascal
26+procedure ArrayLoop(A: Array of Byte);
27+var
28+ B: Byte;
29+begin
30+ for B in A do
31+ DoSomething(B);
32+end;
33+```
34+
35+### Set loop
36+
37+``` pascal
38+type
39+ TColor = (cRed, cGren, cBlue);
40+ TColors = set of TColor;
41+procedure SetLoop(Colors: TColors);
42+var
43+ Color: TColor;
44+begin
45+ for Color in Colors do
46+ DoSomething(Color);
47+end;
48+```
49+
50+### Loop variables are temporary copies of the container value
51+
52+The loop variable in a for-in loop is a [copy of the value that is stored inside a container over which the for-in loop is performed](https://stackoverflow.com/questions/18575736/delphi-dynamic-array-iteration-and-record-copyingsimple).
53+
54+``` pascal
55+program tempcopy;
56+
57+{$IFDEF FPC}
58+ {$mode Delphi}
59+{$ENDIF}
60+
61+uses SysUtils;
62+
63+type
64+ PointerAddress = NativeUInt;
65+
66+var
67+ pIntArr: Pointer;
68+ IntArr: Array of Integer;
69+
70+procedure ArrayLoop(const AArr: Array of Integer);
71+var
72+ i: Integer;
73+begin
74+ pIntArr := @AArr;
75+ writeln('Argument: ', PointerAddress(pIntArr)); // <-- same memory address as Item 1
76+ pIntArr := @i;
77+ writeln('Local loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
78+ for i in AArr do
79+ begin
80+ pIntArr := @i;
81+ writeln('Loop variable: ', PointerAddress(pIntArr)); // <-- local temporary variable i
82+ end;
83+end;
84+
85+begin
86+ // allow 3 items in the array
87+ SetLength(IntArr, 3);
88+
89+ pIntArr := @pIntArr;
90+ writeln('global pIntArr address: ', PointerAddress(pIntArr));
91+
92+ // IntArr is separate variable that points to first element
93+ pIntArr := @IntArr;
94+ writeln('global IntArr address: ', PointerAddress(pIntArr));
95+ pIntArr := @IntArr;
96+ writeln('IntArr points to: ', PointerAddress(pIntArr^));
97+
98+ // addresses of the single items
99+ pIntArr := @IntArr[0];
100+ writeln('Item 1: ', PointerAddress(pIntArr)); // <-- memory address of the first item
101+ pIntArr := @IntArr[1];
102+ writeln('Item 2: ', PointerAddress(pIntArr));
103+ pIntArr := @IntArr[2];
104+ writeln('Item 3: ', PointerAddress(pIntArr));
105+
106+ writeln('array loop');
107+ ArrayLoop(IntArr);
108+end.
109+```
110+
111+### Traversing container
112+
113+To traverse a container class you need to add an enumerator for it. An Enumerator is a class structured according to the following template:
114+
115+``` pascal
116+TSomeEnumerator = class
117+public
118+ function MoveNext: Boolean;
119+ property Current: TSomeType;
120+end;
121+```
122+
123+There are only 2 things required for the enumerator class: a MoveNext method which asks the enumerator to step forward and a **Current** property which can return any appropriate type.
124+
125+Thereafter you need to add the magic GetEnumerator method to the container class which returns an enumerator instance.
126+
127+For example:
128+
129+``` pascal
130+type
131+ TEnumerableTree = class;
132+
133+ TTreeEnumerator = class
134+ private
135+ FTree: TEnumerableTree;
136+ FCurrent: TNode;
137+ public
138+ constructor Create(ATree: TEnumerableTree);
139+ function MoveNext: Boolean;
140+ property Current: TNode read FCurrent;
141+ end;
142+
143+ TEnumerableTree = class
144+ public
145+ function GetEnumerator: TTreeEnumerator;
146+ end;
147+
148+constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
149+begin
150+ inherited Create;
151+ FTree := ATree;
152+ FCurrent := nil;
153+end;
154+
155+function TTreeEnumerator.MoveNext: Boolean;
156+begin
157+ // some logic to get the next node from a tree
158+ if FCurrent = nil then
159+ FCurrent := FTree.GetFirstNode
160+ else
161+ FCurrent := FTree.GetNextNode(FCurrent);
162+ Result := FCurrent <> nil;
163+end;
164+
165+function TEnumerableTree.GetEnumerator: TTreeEnumerator;
166+begin
167+ Result := TTreeEnumerator.Create(Self);
168+ // Note: the Result is automatically freed by the compiler after the loop.
169+end;
170+```
171+
172+After this you are able to execute the following code:
173+
174+``` pascal
175+procedure TreeLoop(ATree: TEnumerableTree);
176+var
177+ ANode: TNode;
178+begin
179+ for ANode in ATree do
180+ DoSomething(ANode);
181+end;
182+```
183+
184+You will find that several basic classes (such as TList, TStrings, TCollection, TComponent ...) already have built-in enumerator support.
185+
186+It is also possible to make any class enumerable if you implement the following interface in your enumerable container class:
187+
188+``` pascal
189+ IEnumerable = interface(IInterface)
190+ function GetEnumerator: IEnumerator;
191+ end;
192+```
193+
194+Where IEnumerator is declared as:
195+
196+``` pascal
197+ IEnumerator = interface(IInterface)
198+ function GetCurrent: TObject;
199+ function MoveNext: Boolean;
200+ procedure Reset;
201+ property Current: TObject read GetCurrent;
202+ end;
203+```
204+
205+### Multiple enumerators for one class
206+
207+You can add additional enumerators to classes, objects and records.
208+
209+Here is an example of adding an enumerator which traverses a TEnumerableTree in reverse order:
210+
211+``` pascal
212+type
213+ TEnumerableTree = class;
214+
215+ TTreeEnumerator = class
216+ ...for traversing in order, see above...
217+ end;
218+
219+ TTreeReverseEnumerator = class
220+ private
221+ FTree: TEnumerableTree;
222+ FCurrent: TNode;
223+ public
224+ constructor Create(ATree: TEnumerableTree);
225+ function MoveNext: Boolean;
226+ property Current: TNode read FCurrent;
227+ function GetEnumerator: TTreeReverseEnumerator; // returns itself
228+ end;
229+
230+ TEnumerableTree = class
231+ public
232+ function GetEnumerator: TTreeEnumerator;
233+ function GetReverseEnumerator: TTreeReverseEnumerator;
234+ end;
235+
236+...see above for an implementation of the TTreeEnumerator...
237+
238+constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
239+begin
240+ inherited Create;
241+ FTree := ATree;
242+end;
243+
244+function TTreeReverseEnumerator.MoveNext: Boolean;
245+begin
246+ // some logic to get the next node from a tree in reverse order
247+ if FCurrent = nil then
248+ FCurrent := FTree.GetLastNode
249+ else
250+ FCurrent := FTree.GetPrevNode(FCurrent);
251+ Result := FCurrent <> nil;
252+end;
253+
254+function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
255+begin
256+ Result := Self;
257+end;
258+
259+function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
260+begin
261+ Result := TTreeReverseEnumerator.Create(Self);
262+ // Note: the Result is freed automatically by the compiler after the loop.
263+end;
264+```
265+
266+After this you are able to execute the following code:
267+
268+``` pascal
269+procedure TreeLoop(ATree: TEnumerableTree);
270+var
271+ ANode: TNode;
272+begin
273+ for ANode in ATree.GetReverseEnumerator do
274+ DoSomething(ANode);
275+end;
276+```
277+
278+## FPC extensions
279+
280+The following code examples illustrate constructs that are implemented only by FPC, constructs which are not supported by Delphi.
281+
282+### Traversing enumeration and subrange types
283+
284+In Delphi, it is [not possible to traverse either enumerated types](http://qc.embarcadero.com/wc/qcmain.aspx?d=106212) or subrange types, whereas in Free Pascal we can write the following:
285+
286+``` pascal
287+type
288+ TColor = (clRed, clBlue, clBlack);
289+ TRange = 'a'..'z';
290+var
291+ Color: TColor;
292+ ch: Char;
293+begin
294+ for Color in TColor do
295+ DoSomething(Color);
296+ for ch in TRange do
297+ DoSomethingOther(ch);
298+end.
299+```
300+
301+An example of this can further be demonstrated by the following code taken from bugreport 0029147, where the type system is disabled because a hardcast is used on a value that is not necessarily part of the enum value:
302+
303+``` pascal
304+type
305+ TSomeEnums = (One, Two, Three);
306+
307+resourcestring
308+ SOne = 'One ape';
309+ STwo = 'Two apes';
310+ SThree = 'Three apes';
311+
312+const
313+ SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
314+ SOne, STwo, SThree);
315+
316+var
317+ i: Integer;
318+ SE: TSomeEnums;
319+
320+begin
321+ for i := 0 to 4 do begin
322+ SE := TSomeEnums(i); // hardcast. i can be higher than 2, but the type system is now disabled
323+ WriteLn(SSomeEnumStrings[SE]);
324+ end;
325+end.
326+```
327+
328+The programmer instructs the compiler that i is of the enum type and the compiler will not check any further, assuming that the programmer knows best.\
329+Of course the programmer is wrong and the compiler would have known better...\
330+By traversing the enumerated type with **for in do** the hard cast is superfluous and the code becomes type safe:
331+
332+``` pascal
333+type
334+ TSomeEnums = (One, Two, Three);
335+
336+resourcestring
337+ SOne = 'One ape';
338+ STwo = 'Two apes';
339+ SThree = 'Three apes';
340+
341+const
342+ SSomeEnumStrings: array [Low(TSomeEnums)..High(TSomeEnums)] of string = (
343+ SOne, STwo, SThree);
344+
345+var
346+ SE: TSomeEnums;
347+begin
348+ for SE in TSomeEnums do
349+ WriteLn(SSomeEnumStrings[SE]);
350+end.
351+```
352+
353+### Declaring enumerators
354+
355+It is also not possible in Delphi to add an enumerator without modifying the class, nor to add an enumerator to the non-class/object/record/interface type. FPC makes this possible using the new syntax operator type Enumerator. As in the following example:
356+
357+``` pascal
358+type
359+ TMyRecord = record F1: Integer; F2: array of TMyType; end;
360+ TMyArrayEnumerator = class
361+ private
362+ function GetCurrent: TMyType;
363+ public
364+ constructor Create(const A: TMyRecord);
365+ property Current: TMyType read GetCurrent;
366+ function MoveNext: Boolean;
367+ end;
368+
369+ // This is new built-in operator.
370+ operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
371+ begin
372+ Result := TMyArrayEnumerator.Create(A);
373+ end;
374+
375+var
376+ A: MyRecord;
377+ V: TMyType
378+begin
379+ for V in A do
380+ DoSomething(V);
381+end.
382+```
383+
384+#### Traversing UTF-8 strings
385+
386+As a particularly useful example, the above extension allows very efficient traversal of UTF-8 strings:
387+
388+``` pascal
389+uses
390+ LazUTF8;
391+interface
392+type
393+ { TUTF8StringAsStringEnumerator
394+ Traversing UTF8 codepoints as strings is useful when you want to know the
395+ exact encoding of the UTF8 character or if you like to use string
396+ constants in your code.
397+ For security reasons you should use the codepoints values (cardinals) instead.
398+ If speed matters, don't use enumerators. Instead use the PChar directly as
399+ shown in the MoveNext method and read about UTF8. It has some interesting features. }
400+
401+ TUTF8StringAsStringEnumerator = class
402+ private
403+ fCurrent: UTF8String;
404+ fCurrentPos, fEndPos: PChar;
405+ function GetCurrent: UTF8String;
406+ public
407+ constructor Create(const A: UTF8String);
408+ property Current: UTF8String read GetCurrent;
409+ function MoveNext: Boolean;
410+ end;
411+
412+ operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
413+
414+var
415+ Form1: TForm1;
416+
417+implementation
418+
419+operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
420+begin
421+ Result := TUTF8StringAsStringEnumerator.Create(A);
422+end;
423+
424+{ TUTF8StringAsStringEnumerator }
425+
426+function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
427+begin
428+ Result:=fCurrent;
429+end;
430+
431+constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
432+begin
433+ fCurrentPos:=PChar(A); // Note: if A='' then PChar(A) returns a pointer to a #0 string
434+ fEndPos:=fCurrentPos+length(A);
435+end;
436+
437+function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
438+var
439+ l: Integer;
440+begin
441+ if fCurrentPos<fEndPos then
442+ begin
443+ l:=UTF8CharacterLength(fCurrentPos);
444+ SetLength(fCurrent,l);
445+ Move(fCurrentPos^,fCurrent[1],l);
446+ inc(fCurrentPos,l);
447+ Result:=true;
448+ end else
449+ Result:=false;
450+end;
451+
452+{ TForm1 }
453+
454+procedure TForm1.FormCreate(Sender: TObject);
455+var
456+ s, ch: UTF8String;
457+ i: SizeInt;
458+begin
459+ s:='mäßig';
460+
461+ // using UTF8Length and UTF8Copy this way is slow, requiring O(n)^2
462+ for i:=1 to UTF8Length(s) do
463+ writeln('ch=',UTF8Copy(s,i,1));
464+
465+ // using the above enumerator is shorter and quite fast, requiring O(n)
466+ for ch in s do
467+ writeln('ch=',ch);
468+end;
469+```
470+
471+### Using any identifiers instead of builtin MoveNext and Current
472+
473+In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is enabled by the use of the **enumerator** modifier, with the syntax 'enumerator MoveNext;' and 'enumerator Current;' modifiers. As in the following example:
474+
475+``` pascal
476+type
477+ { TMyListEnumerator }
478+
479+ TMyListEnumerator = object
480+ private
481+ FCurrent: Integer;
482+ public
483+ constructor Create;
484+ destructor Destroy;
485+ function StepNext: Boolean; enumerator MoveNext;
486+ property Value: Integer read FCurrent; enumerator Current;
487+ end;
488+
489+ TMyList = class
490+ end;
491+
492+{ TMyListEnumerator }
493+
494+constructor TMyListEnumerator.Create;
495+begin
496+ FCurrent := 0;
497+end;
498+
499+destructor TMyListEnumerator.Destroy;
500+begin
501+ inherited;
502+end;
503+
504+function TMyListEnumerator.StepNext: Boolean;
505+begin
506+ inc(FCurrent);
507+ Result := FCurrent <= 3;
508+end;
509+
510+operator enumerator (AList: TMyList): TMyListEnumerator;
511+begin
512+ Result.Create;
513+end;
514+
515+var
516+ List: TMyList;
517+ i: integer;
518+begin
519+ List := TMyList.Create;
520+ for i in List do
521+ WriteLn(i);
522+ List.Free;
523+end.
524+```
525+
526+## Proposed extensions
527+
528+### Get enumerator Position if available
529+
530+It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as the current index, might be useful:
531+
532+``` pascal
533+type
534+ TUTF8StringEnumerator = class
535+ private
536+ FByteIndex: Integer;
537+ FCharIndex: Integer;
538+ public
539+ constructor Create(const A: UTF8String);
540+ function Current: UTF8Char;
541+ function CurrentIndex: Integer;
542+ function MoveNext: Boolean;
543+ end;
544+
545+ operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
546+ begin
547+ Result := TUTF8String.Create(A);
548+ end;
549+
550+var
551+ s: UTF8String;
552+ ch: UTF8Char;
553+ i: Integer;
554+begin
555+
556+ // Inefficient, as discussed above
557+ for i := 1 to Length(s) do
558+ Writeln(i, ': ', ch[i]);
559+
560+ // Ok, but ugly
561+ i := 1;
562+ for ch in s do begin
563+ Writeln(i, ': ', ch);
564+ Inc(i);
565+ end;
566+
567+ // Proposed extension
568+ for ch in s index i do
569+ Writeln(i, ': ', ch);
570+
571+ // Proposed extension for traversing backwards (equivalent to downto)
572+ for ch in reverse s do
573+ Writeln(i, ': ', ch);
574+
575+ // With proposed index extension
576+ for ch in reverse s index i do
577+ Writeln(i, ': ', ch);
578+end.
579+```
580+
581+Note that index could be designed to return an arbitrary type (i. e. not necessarily an integer). For example, in the case of tree traversal, the index might return an array of nodes describing the path from the tree root to the current node.
582+
583+## Reference
584+
585+- <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html>
586+
587+---
588+*From the Free Pascal wiki, page "for-in loop", as it stood on 2021-04-10 after a change by Bi0T1N, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history