Difference between revisions of "for-in loop"

From Lazarus wiki
Jump to navigationJump to search
m (Text replace - "delphi>" to "syntaxhighlight>")
Line 9: Line 9:
 
=== String loop ===
 
=== String loop ===
  
<delphi>
+
<syntaxhighlight>
 
procedure StringLoop(S: String);
 
procedure StringLoop(S: String);
 
var
 
var
Line 17: Line 17:
 
     DoSomething(C);
 
     DoSomething(C);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Array loop ===
 
=== Array loop ===
  
<delphi>
+
<syntaxhighlight>
 
procedure ArrayLoop(A: Array of Byte);
 
procedure ArrayLoop(A: Array of Byte);
 
var
 
var
Line 29: Line 29:
 
     DoSomething(B);
 
     DoSomething(B);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Set loop ===
 
=== Set loop ===
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TColor = (cRed, cGren, cBlue);
 
   TColor = (cRed, cGren, cBlue);
Line 44: Line 44:
 
     DoSomething(Color);
 
     DoSomething(Color);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
=== Traversing container ===
 
=== Traversing container ===
Line 50: Line 50:
 
To traverse some container class you need to add an <b>enumerator</b> for it. <b>Enumerator</b> is a class built by the next template:
 
To traverse some container class you need to add an <b>enumerator</b> for it. <b>Enumerator</b> is a class built by the next template:
  
<delphi>
+
<syntaxhighlight>
 
TSomeEnumerator = class
 
TSomeEnumerator = class
 
public
 
public
Line 56: Line 56:
 
   property Current: TSomeType;
 
   property Current: TSomeType;
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
There are only 2 things required for the enumerator: MoveNext method which asks enumerator to step forward and property Current which can return any desired type.
 
There are only 2 things required for the enumerator: MoveNext method which asks enumerator to step forward and property Current which can return any desired type.
Line 63: Line 63:
  
 
For example:
 
For example:
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TEnumerableTree = class;
 
   TEnumerableTree = class;
Line 104: Line 104:
 
end;
 
end;
  
</delphi>
+
</syntaxhighlight>
  
 
After this you are able to execute the next code:
 
After this you are able to execute the next code:
  
<delphi>
+
<syntaxhighlight>
 
procedure TreeLoop(ATree: TEnumerableTree);
 
procedure TreeLoop(ATree: TEnumerableTree);
 
var
 
var
Line 116: Line 116:
 
     DoSomething(ANode);
 
     DoSomething(ANode);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
Of course enumerator support is built into the basic classes: TList, TStrings, TCollection, TComponent, ...
 
Of course enumerator support is built into the basic classes: TList, TStrings, TCollection, TComponent, ...
  
 
It is also possible to make some class enumerable if you implement the next interface for the container:
 
It is also possible to make some class enumerable if you implement the next interface for the container:
<delphi>
+
<syntaxhighlight>
 
   IEnumerable = interface(IInterface)
 
   IEnumerable = interface(IInterface)
 
     function GetEnumerator: IEnumerator;
 
     function GetEnumerator: IEnumerator;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
  
 
Where <b>IEnumerator</b> is declared as:
 
Where <b>IEnumerator</b> is declared as:
<delphi>
+
<syntaxhighlight>
 
   IEnumerator = interface(IInterface)
 
   IEnumerator = interface(IInterface)
 
     function GetCurrent: TObject;
 
     function GetCurrent: TObject;
Line 135: Line 135:
 
     property Current: TObject read GetCurrent;
 
     property Current: TObject read GetCurrent;
 
   end;
 
   end;
</delphi>
+
</syntaxhighlight>
  
 
=== Multiple enumerators for one class ===
 
=== Multiple enumerators for one class ===
Line 143: Line 143:
 
Example for adding an enumerator to traverse the tree in reverse order:
 
Example for adding an enumerator to traverse the tree in reverse order:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TEnumerableTree = class;
 
   TEnumerableTree = class;
Line 195: Line 195:
 
   Result := TTreeReverseEnumerator.Create(Self);
 
   Result := TTreeReverseEnumerator.Create(Self);
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
After this you are able to execute the next code:
 
After this you are able to execute the next code:
  
<delphi>
+
<syntaxhighlight>
 
procedure TreeLoop(ATree: TEnumerableTree);
 
procedure TreeLoop(ATree: TEnumerableTree);
 
var
 
var
Line 207: Line 207:
 
     DoSomething(ANode);
 
     DoSomething(ANode);
 
end;  
 
end;  
</delphi>
+
</syntaxhighlight>
  
 
== FPC extensions ==
 
== FPC extensions ==
Line 216: Line 216:
 
In Delphi, it is not possible to traverse either enumerated types or range types, whereas in Free Pascal we can write the following:
 
In Delphi, it is not possible to traverse either enumerated types or range types, whereas in Free Pascal we can write the following:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TColor = (clRed, clBlue, clBlack);
 
   TColor = (clRed, clBlue, clBlack);
Line 229: Line 229:
 
     DoSomethingOther(ch);
 
     DoSomethingOther(ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
=== Declaring enumerators ===
 
=== Declaring enumerators ===
Line 236: Line 236:
 
It is possible in FPC by adding new <b>operator</b> type <b>Enumerator</b>. Like in the next example:
 
It is possible in FPC by adding new <b>operator</b> type <b>Enumerator</b>. Like in the next example:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TMyRecord = record F1: Integer; F2: array of TMyType; end;
 
   TMyRecord = record F1: Integer; F2: array of TMyType; end;
Line 261: Line 261:
 
     DoSomething(V);
 
     DoSomething(V);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
As a particularly useful example, the above extension would allow
 
As a particularly useful example, the above extension would allow
 
to traverse UTF-8 strings efficiently:
 
to traverse UTF-8 strings efficiently:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TUTF8StringEnumerator = class
 
   TUTF8StringEnumerator = class
Line 296: Line 296:
 
     DoSomething(ch);
 
     DoSomething(ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
=== Using any identifiers instead of builtin MoveNext and Current ===
 
=== Using any identifiers instead of builtin MoveNext and Current ===
 
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 solved this by using 'enumerator MoveNext' and 'enumerator Current' modifiers. Like in the next example:
 
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 solved this by using 'enumerator MoveNext' and 'enumerator Current' modifiers. Like in the next example:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   { TMyListEnumerator }
 
   { TMyListEnumerator }
Line 350: Line 350:
 
   List.Free;
 
   List.Free;
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
== Proposed extensions ==
 
== Proposed extensions ==
Line 359: Line 359:
 
except the current item. Sometimes other data, such as current index, may be useful:
 
except the current item. Sometimes other data, such as current index, may be useful:
  
<delphi>
+
<syntaxhighlight>
 
type
 
type
 
   TUTF8StringEnumerator = class
 
   TUTF8StringEnumerator = class
Line 398: Line 398:
 
     Writeln(i, ': ', ch);
 
     Writeln(i, ': ', ch);
 
end.
 
end.
</delphi>
+
</syntaxhighlight>
  
 
Note that index might return arbitrary type, not necessarily integer.
 
Note that index might return arbitrary type, not necessarily integer.

Revision as of 10:27, 24 March 2012

English (en) français (fr) 日本語 (ja) русский (ru)

"for-in" loop exists in delphi starting from 2005 version. It is implemented now in FPC 2.4.2.

Delphi and FPC implementation

It has the next syntax:

String loop

procedure StringLoop(S: String);
var
  C: Char;
begin
  for C in S do
    DoSomething(C);
end;

Array loop

procedure ArrayLoop(A: Array of Byte);
var
  B: Byte;
begin
  for B in A do
    DoSomething(B);
end;

Set loop

type
  TColor = (cRed, cGren, cBlue);
  TColors = set of TColor;
procedure SetLoop(Colors: TColors);
var
  Color: TColor;
begin
  for Color in Colors do
    DoSomething(Color);
end;

Traversing container

To traverse some container class you need to add an enumerator for it. Enumerator is a class built by the next template:

TSomeEnumerator = class
public
  function MoveNext: Boolean;
  property Current: TSomeType;
end;

There are only 2 things required for the enumerator: MoveNext method which asks enumerator to step forward and property Current which can return any desired type.

Next thing is to add magic GetEnumerator method to the container class which returns an enumerator instance.

For example:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
  end;

constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
  FCurrent := nil;
end;

function TTreeEnumerator.MoveNext: Boolean;
begin
  // some logic to get the next node from a tree
  if FCurrent = nil then
    FCurrent := FTree.GetFirstNode
  else
    FCurrent := FTree.GetNextNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TEnumerableTree.GetEnumerator: TTreeEnumerator;
begin
  Result := TTreeEnumerator.Create(Self);
end;

After this you are able to execute the next code:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree do
    DoSomething(ANode);
end;

Of course enumerator support is built into the basic classes: TList, TStrings, TCollection, TComponent, ...

It is also possible to make some class enumerable if you implement the next interface for the container:

  IEnumerable = interface(IInterface)
    function GetEnumerator: IEnumerator;
  end;

Where IEnumerator is declared as:

  IEnumerator = interface(IInterface)
    function GetCurrent: TObject;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: TObject read GetCurrent;
  end;

Multiple enumerators for one class

You can add additional enumerators to classes, objects and records.

Example for adding an enumerator to traverse the tree in reverse order:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  ...for traversing in order, see above...
  end;

  TTreeReverseEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
    function GetEnumerator: TTreeReverseEnumerator; // returns itself
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
    function GetReverseEnumerator: TTreeReverseEnumerator;
  end;

...see above for implementation of the TTreeEnumerator...

constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
end;

function TTreeReverseEnumerator.MoveNext: Boolean;
begin
  // some logic to get the next node from a tree in reverse order
  if FCurrent = nil then
    FCurrent := FTree.GetLastNode
  else
    FCurrent := FTree.GetPrevNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
begin
  Result := Self;
end;

function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
begin
  Result := TTreeReverseEnumerator.Create(Self);
end;

After this you are able to execute the next code:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree.GetReverseEnumerator do
    DoSomething(ANode);
end;

FPC extensions

The following examples are not supported by Delphi, and implemented in FPC only.

Traverse enumerations and ranges

In Delphi, it is not possible to traverse either enumerated types or range types, whereas in Free Pascal we can write the following:

type
  TColor = (clRed, clBlue, clBlack);
  TRange = 'a'..'z';
var
  Color: TColor;
  ch: Char;
begin
  for Color in TColor do
    DoSomething(Color);
  for ch in TRange do
    DoSomethingOther(ch);
end.

Declaring enumerators

It is also not possible in Delphi to add an enumerator without modifying the class, as well as add an enumerator to the non-class/object/record/interface type. It is possible in FPC by adding new operator type Enumerator. Like in the next example:

type
  TMyRecord = record F1: Integer; F2: array of TMyType; end;
  TMyArrayEnumerator = class
  private
    function GetCurrent: TMyType;
  public
    constructor Create(const A: TMyRecord);
    property Current: TMyType read GetCurrent;
    function MoveNext: Boolean;
  end;

  // This is new built-in operator.
  operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
  begin
    Result := TMyArrayEnumerator.Create(A);
  end;

var
  A: MyRecord;
  V: TMyType
begin
  for V in A do
    DoSomething(V);
end.

As a particularly useful example, the above extension would allow to traverse UTF-8 strings efficiently:

type
  TUTF8StringEnumerator = class
  private
    FByteIndex: Integer;
    FCharIndex: Integer;
    function GetCurrent: UTF8Char;
  public
    constructor Create(const A: UTF8String);
    property Current: UTF8Char read GetCurrent;
    function MoveNext: Boolean;
  end;

  operator Enumerator(A: UTF8String): TUTF8StringEnumerator;
  begin
    Result := TUTF8StringEnumerator.Create(A);
  end;

var
  s: UTF8String;
  ch: UTF8Char;
  i: Integer;
begin
  // This requires O(N^2) operations
  for i := 1 to Length(s) do
    DoSomething(ch[i]);
  // This requires only O(N) operations
  for ch in s do
    DoSomething(ch);
end.

Using any identifiers instead of builtin MoveNext and Current

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 solved this by using 'enumerator MoveNext' and 'enumerator Current' modifiers. Like in the next example:

type
  { TMyListEnumerator }

  TMyListEnumerator = object
  private
    FCurrent: Integer;
  public
    constructor Create;
    destructor Destroy;
    function StepNext: Boolean; enumerator MoveNext;
    property Value: Integer read FCurrent; enumerator Current;
  end;

  TMyList = class
  end;

{ TMyListEnumerator }

constructor TMyListEnumerator.Create;
begin
  FCurrent := 0;
end;

destructor TMyListEnumerator.Destroy;
begin
  inherited;
end;

function TMyListEnumerator.StepNext: Boolean;
begin
  inc(FCurrent);
  Result := FCurrent <= 3;
end;

operator enumerator (AList: TMyList): TMyListEnumerator;
begin
  Result.Create;
end;

var
  List: TMyList;
  i: integer;
begin
  List := TMyList.Create;
  for i in List do
    WriteLn(i);
  List.Free;
end.

Proposed extensions

Get enumerator Position if available

It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as current index, may be useful:

type
  TUTF8StringEnumerator = class
  private
    FByteIndex: Integer;
    FCharIndex: Integer;
  public
    constructor Create(const A: UTF8String);
    function Current: UTF8Char;
    function CurrentIndex: Integer;
    function MoveNext: Boolean;
  end;

  operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
  begin
    Result := TUTF8String.Create(A);
  end;

var
  s: UTF8String;
  ch: UTF8Char;
  i: Integer;
begin

  // Inefficient, as discussed above
  for i := 1 to Length(s) do
    Writeln(i, ': ', ch[i]);

  // Ok, but ugly
  i := 1;
  for ch in s do begin
    Writeln(i, ': ', ch);
    Inc(i);
  end;

  // Proposed extension
  for ch in s index i do
    Writeln(i, ': ', ch);
end.

Note that index might return arbitrary type, not necessarily integer. For example, in the case of tree traversal, the index might return an array of nodes from on the path from the tree root to the current node.

Reference