In the next release of Delphi, the compiler will support a new kind of for loop, dubbed the “for..in” loop. A “for..in” loop works just like a regular Delphi for loop except that you don’t have to deal with a loop index variable. If you want to run through all the strings in a collection object, you just say “Here’s the collection, here’s where I want to see each string, now gimme!”
Here’s the syntax:
for <variable> in <container expression> do <statement>;
where <variable> is a local variable of the type of the data in the container and <container> is an expression whose type either implements the IEnumerable interface, or implements a pattern that the compiler recognizes as an enumerator, or is a type that the compiler recognizes as being a container of data items, such as an array. If you don’t know what <statement> refers to, you will be feeling very sleepy about now.
type TStringArray = array of String; procedure Demo1(const List: TStringArray); var S: String; begin for S in List do writeln(S); end;
The code above outputs all of the strings provided in the given array of strings. Note that you don’t have to declare a loop control variable or use it to index into the array to fetch each item – the compiler does that for you behind the scenes.
procedure Demo2(List: System.Collections.Specialized.StringCollection); var S: String; begin for S in List do writeln(S); end;
The Demo2 procedure does the same thing as Demo1, but instead of pulling the strings out of an array Demo2 pulls the strings out of a .NET StringCollection object. The compiler looks at the StringCollection type to see if it implements either the enumerator pattern (a visible method named GetEnumerator and a few other details) or if the type implements the IEnumerable interface, and generates the machine code necessary to retrieve the enumerator interface and walk through each item in the collection.
The good news is, you don’t need to care about those details of how to get to all of the items. You just have to care about the type of the data in the collection and what you’re going to do with all/each item.
As you have probably figured out by now, this for..in syntax provides much the same functionality that other languages refer to as a “for each” loop. However, we didn’t stop with <container expression> limited to a object instance that implements an interface or enumerator pattern. The Delphi for..in syntax also supports data types that the Delphi compiler knows contain multiple elements of a uniform data type: arrays (of elements), strings (of char), and even sets (of enums).
Here’s a fun example with sets:
type TRole = (worker, manager, executioner, CEO); TJobRoles = set of TRole; procedure Demo3(const JobRoles: TJobRoles); var R: TRole; begin for R in JobRoles do // use R here; end;
This Demo3 routine will execute the code in the loop only for the values of R that are included in the set variable JobRoles. It’s equivalent to a loop that runs through all the possible values in the enum (for x := Low(TRole) to High(TRole) do) and tests JobRoles for each of them.
Oh, and for..in handles multidimensional arrays, too:
type TMultiArray = array [0..10, 0..3, 0..9] of Integer; procedure DemoMulti(const table: TMultiArray); var X: Integer; begin for X in table do writeln(x); end;
This procedure DemoMulti runs through all 440 cells in the TMultiArray array, rightmost index first. That is, the value of table[0,0,0], then table[0,0,1], then table[0,0,2] and so forth. The codegen is exactly as if you had written three nested for loops, each with their own index variable. But who cares? You focus on what you want to do with each item and let the compiler worry about how to get them all to you.
Why For..In? Why not Foreach / For Each / ForEver?
Counter question: Why “each”? Why not “For All x in foo” or “For Every x in foo” in the language of good old mathematical proofs?
The reason: it wasn’t necessary. The for..in syntax can be parsed and distinguished from the other for loop just fine without pulling another reserved word out of circulation. Why make things more complicated than they need to be?
Idea: Implement the enumerator pattern or interface on a dataset class. Poof! for DataRow in SQLQuery do …
Idea: Enumerable file/directory search class. Poof! for filename in FileSearch(*.pas) do …
Idea: For emailAddress in OutlookAddressBook do Spam.a.Matic(…)
p.s. One last little tease: this for..in syntax is not limited to the .NET platform. It works just peachy in the Win32 Delphi compiler as well!
See It First At BorCon 2004
I’ll be discussing these and several other language and compiler enhancements for the Win32 and .NET Delphi compilers at BorCon in September.