As of version 2.4.2, Free Pascal supports the For..in loop construction. A for..in loop is used in case one wants to calculate something a fixed number of times with an enumerable loop variable. The prototype syntax is as follows:
_________________________________________________________________________________________________________
For in statement
____________________________________________
Here, Statement can be a compound statement. The enumerable must be an expression that consists of a fixed number of elements: the loop variable will be made equal to each of the elements in turn and the statement following the do keyword will be executed.
The enumerable expression can be one of five cases:
An enumeration type identifier. The loop will then be over all elements of the enumeration type. The control variable must be of the enumeration type.
A set value. The loop will then be over all elements in the set, the control variable must be of the base type of the set.
An array value. The loop will be over all elements in the array, and the control variable must have the same type as an element in the array. As a special case, a string is regarded as an array of characters.
An enumerable class, object, or extended record instance. This is an instance of any structured type that supports the IEnumerator and IEnumerable interfaces. In this case, the control variable’s type must equal the type of the IEnumerator.GetCurrent return value.
Any type for which an enumerator operator is defined. The enumerator operator must return a structured type that implements the IEnumerator interface. The type of the control variable’s type must equal the type of the enumerator’s GetCurrent return value type.
The simplest case of the for..in loop is using an enumerated type:
Type TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday); Var d : TWeekday; begin for d in TWeekday do writeln(d); end.
This will print all week days to the screen.
The above for..in construct is equivalent to the following for..to construct:
Type TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday); Var d : TWeekday; begin for d:=Low(TWeekday) to High(TWeekday) do writeln(d); end.
A second case of for..in loop is when the enumerable expression is a set, and then the loop will be executed once for each element in the set:
Type TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday); Var Week : set of TWeekDay = [monday, tuesday, wednesday, thursday, friday]; d : TWeekday; begin for d in Week do writeln(d); end.
This will print the names of the week days to the screen. Note that the variable d is of the same type as the base type of the set.
The above for..in construct is equivalent to the following for..to construct:
Type TWeekDay = (monday, tuesday, wednesday, thursday, friday,saturday,sunday); Var Week : set of TWeekDay = [monday, tuesday, wednesday, thursday, friday]; d : TWeekday; begin for d:=Low(TWeekday) to High(TWeekday) do if d in Week then writeln(d); end.
The third possibility for a for..in loop is when the enumerable expression is an array:
var a : Array[1..7] of string = ('monday','tuesday','wednesday','thursday', 'friday','saturday','sunday'); Var S : String; begin For s in a do Writeln(s); end.
This will also print all days in the week, and is equivalent to
var a : Array[1..7] of string = ('monday','tuesday','wednesday','thursday', 'friday','saturday','sunday'); Var i : integer; begin for i:=Low(a) to high(a) do Writeln(a[i]); end.
A string type is equivalent to an array of char, and therefore a string can be used in a for..in loop. The following will print all letters in the alphabet, each letter on a line:
Var c : char; begin for c in 'abcdefghijklmnopqrstuvwxyz' do writeln(c); end.
Note that multi-dimensional arrays are also supported:
uses SysUtils; type TTestStringArray = array[0..10] of String; Var TwoD : array[0..3] of TTestStringArray; var i,j : integer; S : String; begin for i:=0 to 3 do for j:=0 to 10 do TwoD[i,J]:=Format('%.2dx%.2d',[i,j]); for S in twod do Writeln(S); end.
This will loop over all dimensions from left to right.
The fourth possibility for a for..in loop is using classes. A class can implement the IEnumerable interface, which is defined as follows:
IEnumerable = interface(IInterface) function GetEnumerator: IEnumerator; end;
The actual return type of the GetEnumerator must not necessarily be an IEnumerator interface, instead, it can be a class which implements the methods of IEnumerator:
IEnumerator = interface(IInterface) function GetCurrent: TObject; function MoveNext: Boolean; procedure Reset; property Current: TObject read GetCurrent; end;
The Current property and the MoveNext method must be present in the class returned by the GetEnumerator method. The actual type of the Current property need not be a TObject. When encountering a for..in loop with a class instance as the “in” operand, the compiler will check each of the following conditions:
Whether the class in the enumerable expression implements a method GetEnumerator
Whether the result of GetEnumerator is a class with the following method:
Function MoveNext : Boolean
Whether the result of GetEnumerator is a class with the following read-only property:
Property Current : AType;
The type of the property must match the type of the control variable of the for..in loop.
Neither the IEnumerator nor the IEnumerable interfaces must actually be declared by the enumerable class: the compiler will detect whether these interfaces are present using the above checks. The interfaces are only defined for Delphi compatibility and are not used internally. (it would also be impossible to enforce their correctness).
The Classes unit contains a number of classes that are enumerable:
Enumerates all pointers in the list.
Enumerates all pointers in the list.
Enumerates all items in the collection.
Enumerates all strings in the list.
Enumerates all child components owned by the component.
Thus, the following code will also print all days in the week:
{$mode objfpc} uses classes; Var Days : TStrings; D : String; begin Days:=TStringList.Create; try Days.Add('Monday'); Days.Add('Tuesday'); Days.Add('Wednesday'); Days.Add('Thursday'); Days.Add('Friday'); Days.Add('Saturday'); Days.Add('Sunday'); For D in Days do Writeln(D); Finally Days.Free; end; end.
Note that the compiler enforces type safety: declaring D as an integer will result in a compiler error:
testsl.pp(20,9) Error: Incompatible types: got "AnsiString" expected "LongInt"
The above code is equivalent to the following:
{$mode objfpc} uses classes; Var Days : TStrings; D : String; E : TStringsEnumerator; begin Days:=TStringList.Create; try Days.Add('Monday'); Days.Add('Tuesday'); Days.Add('Wednesday'); Days.Add('Thursday'); Days.Add('Friday'); Days.Add('Saturday'); Days.Add('Sunday'); E:=Days.getEnumerator; try While E.MoveNext do begin D:=E.Current; Writeln(D); end; Finally E.Free; end; Finally Days.Free; end; end.
Both programs will output the same result.
The fifth and last possibility to use a for..in loop can be used to enumerate almost any type, using the enumerator operator. The enumerator operator must return a class that has the same signature as the IEnumerator approach above. The following code will define an enumerator for the Integer type:
Type TEvenEnumerator = Class FCurrent : Integer; FMax : Integer; Function MoveNext : Boolean; Property Current : Integer Read FCurrent; end; Function TEvenEnumerator.MoveNext : Boolean; begin FCurrent:=FCurrent+2; Result:=FCurrent<=FMax; end; operator enumerator(i : integer) : TEvenEnumerator; begin Result:=TEvenEnumerator.Create; Result.FMax:=i; end; var I : Integer; m : Integer = 4; begin For I in M do Writeln(i); end.
The loop will print all nonzero even numbers smaller or equal to the enumerable. (2 and 4 in the case of the example).
Care must be taken when defining enumerator operators: the compiler will find and use the first available enumerator operator for the enumerable expression. For classes this also means that the GetEnumerator method is not even considered. The following code will define an enumerator operator which extracts the object from a stringlist:
{$mode objfpc} uses classes; Type TDayObject = Class DayOfWeek : Integer; Constructor Create(ADayOfWeek : Integer); end; TObjectEnumerator = Class FList : TStrings; FIndex : Integer; Function GetCurrent : TDayObject; Function MoveNext: boolean; Property Current : TDayObject Read GetCurrent; end; Constructor TDayObject.Create(ADayOfWeek : Integer); begin DayOfWeek:=ADayOfWeek; end; Function TObjectEnumerator.GetCurrent : TDayObject; begin Result:=FList.Objects[Findex] as TDayObject; end; Function TObjectEnumerator.MoveNext: boolean; begin Inc(FIndex); Result:=(FIndex<FList.Count); end; operator enumerator (s : TStrings) : TObjectEnumerator; begin Result:=TObjectEnumerator.Create; Result.Flist:=S; Result.FIndex:=-1; end; Var Days : TStrings; D : String; O : TdayObject; begin Days:=TStringList.Create; try Days.AddObject('Monday',TDayObject.Create(1)); Days.AddObject('Tuesday',TDayObject.Create(2)); Days.AddObject('Wednesday',TDayObject.Create(3)); Days.AddObject('Thursday',TDayObject.Create(4)); Days.AddObject('Friday',TDayObject.Create(5)); Days.AddObject('Saturday',TDayObject.Create(6)); Days.AddObject('Sunday',TDayObject.Create(7)); For O in Days do Writeln(O.DayOfWeek); Finally Days.Free; end; end.
The above code will print the day of the week for each day in the week.
If a class is not enumerable, the compiler will report an error when it is encountered in a for...in loop.
Remark Like the for..to loop, it is not allowed to change (i. e. assign a value to) the value of a loop control variable inside the loop.