Amazon.com Widgets Loop Less with IEnumerable and Spring for Delphi

Loop Less with IEnumerable and Spring for Delphi

By Nick at January 06, 2013 19:57
Filed Under: Delphi, Software Development

I rave about the Delphi Spring Framework.  But I think my attention to the Dependency Injection Framework – which is only part of it -- has made you guys think that the two are synonymous.  Not true – Spring4D is more than just a Dependency Injection container.  In fact, there is a wealth of other stuff to be found in Spring4D.

Inspired by this StackOverflow question and a thread in the newsgroups, I thought that I’d write an article about a very powerful but not so well known (apparently) feature of Spring4D – IEnumerable<T> and the accompanying container interfaces and classes.

So the StackOverflow question basically asks this: How do I find out the maximum value of an integer in TList<integer>?

Well, the first thing you might do is use a for loop to check each item, and save each one as it gets larger.  Or if you were really tricky, you’d use a for…in statement to find it.  That’s the “straight-up” way to do it.

But that isn’t the real answer to the StackOverflow question.  The real answer is this:

program ListEnumerableDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses 
    System.SysUtils 
  , Spring.Collections;

var 
  List: IList<Integer>; 
  Enumerable: IEnumerable<integer>;

begin 
  try 
    List := TCollections.CreateList<integer>; 
    List.AddRange([1,6,2,9,54,3,2,7,9,1]);

    Enumerable := List; 
    WriteLn(Enumerable.Max); 
    ReadLn; 
  except 
    on E: Exception do 
      Writeln(E.ClassName, ': ', E.Message); 
  end; 
end. 

 

Now that looks quite a bit different than the code you were thinking of, I’ll bet.   There doesn’t even appear to be any looping going on at all.  There is in the background, but it’s all been abstracted away for you.  You want the maximum value to be found in the list?  Just ask for it.  Cool.

The key part above, of course, is the use of two things.  First, the IList<T> – an interface to a generic list – and secondly, the IEnumerable<T>.  Both of these types are provided by the Delphi Spring Framework.  Both are defined in the Spring.Collections.pas unit.  That unit contains a complete group of collection interfaces – IList<T>, IStack<T> ICollection<T>, etc. – as well as the definition of IEnumerable<T>. 

I’d recommend that you consider using Spring.Collections.pas instead of the Generics.Collections.pas unit which comes with Delphi.  First, it exposes all of the collection types as interfaces, and you should know by now that you should be coding against interfaces and not implementations.  Secondly, each of the types supports the IEnumerable<T> interface, meaning you have the power of this great interface on all of your collections.

We’ll tackle those two things in order.

Spring Collections

The Spring.Collections.pas unit is all you need in your uses clause to avail yourself of all that the Spring collections library provides.  The unit includes the following interfaces along with a class to provide easy access to implementations of these interfaces:

Interface Description
ICollection<T> A Collection is a group of items in no particular order.  You can't insert items into a specific location in a collection, only add or remove them.  A collection can’t be sorted because it has no order.
IList<T> A list is a group of items in a particular order.  Items can be sorted and inserted in particular locations.
IDictionary<TKey, TValue> A dictionary is a data structure that allows you to “look up” items.  Each entry takes a key and a value, and values can be looked up by asking for it via the item’s key.  This is a useful data structure, as any type can be uses as the index, and any type can be stored for lookup.
IStack<T> A stack is a First In, Last Out data structure. Think of a spring-loaded stack of plates at the cafeteria – you “push” plates onto the stack, and then “pop” them off in reverse order.
IQueue<T> A queue is a First In, First Out data structure.  Think of an ordinary line that you wait in, or a tube in which items are inserted at one end and taken out on the other.
ISet<T> A set is a data structure involving membership.  Items are either in the set or out.  A set’s methods allow you to determine the intersection, union, or overlap with any other container.

 

All of these types should be familiar to you – if not, you can certainly read up on them via Google.  The important part is that they are all generic (though there are non-generic types with store TValue as well) and they all implement IEnumerable<T>.

The unit also provides a class called TCollections which has nothing but class methods that return instances of the types listed above.  These methods act as proxies for the constructors of the implementing types.  You can create your own instances if you like, but for the most part, TCollections will do the job of getting an implementation of whatever container interface you want.  For instance, if you want to get a hold of a dictionary, you can do the following:

program ListEnumerableDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses 
    System.SysUtils 
  , Spring.Collections;

var 
  List: IList<Integer>; 
  Enumerable: IEnumerable<integer>;

begin 
  try 
    List := TCollections.CreateList<integer>; 
    List.AddRange([1,6,2,9,54,3,2,7,9,1]);

    Enumerable := List; 
    WriteLn(Enumerable.Max); 
    ReadLn; 
  except 
    on E: Exception do 
      Writeln(E.ClassName, ': ', E.Message); 
  end; 
end. 

The example above uses TCollections.CreateList<integer> to get an implementation of the IList<T> interface.

The implementations are all in units named Spring.Collections.Stack.pas and the like, but the Spring.Collections.pas unit exposes almost everything you need, and so as a rule, you should only need it in your uses clauses to take advantage of these powerful containers.

 

IEnumerable<T>

Those of you who also use .Net are familiar with IEnumerable already, but perhaps you didn't know that the same power was available to you in your Delphi code.  For those of you not familiar, you are in for a treat.

The IEnumerable<T> interface is implemented by all of the classes discussed in the previous section, and so any of them can be accessed as an IEnumerable<T>. 

Here is the declaration of IEnumerable<T>:

IEnumerable<T> = interface(IEnumerable)
    function GetEnumerator: IEnumerator<T>;
    function AsObject: TObject;
    function TryGetFirst(out value: T): Boolean;
    function TryGetLast(out value: T): Boolean;
    function First: T; overload;
    function First(const predicate: TPredicate<T>): T; overload;
    function FirstOrDefault: T; overload;
    function FirstOrDefault(const defaultValue: T): T; overload;
    function FirstOrDefault(const predicate: TPredicate<T>): T; overload;
    function Last: T; overload;
    function Last(const predicate: TPredicate<T>): T; overload;
    function LastOrDefault: T; overload;
    function LastOrDefault(const defaultValue: T): T; overload;
    function LastOrDefault(const predicate: TPredicate<T>): T; overload;
    function Single: T; overload;
    function Single(const predicate: TPredicate<T>): T; overload;
    function SingleOrDefault: T; overload;
    function SingleOrDefault(const predicate: TPredicate<T>): T; overload;
    function ElementAt(index: Integer): T;
    function ElementAtOrDefault(index: Integer): T;
    function All(const predicate: TPredicate<T>): Boolean;
    function Any(const predicate: TPredicate<T>): Boolean;
    function Contains(const item: T): Boolean; overload;
    function Contains(const item: T; const comparer: IEqualityComparer<T>): Boolean; overload;
    function Min: T;
    function Max: T;
    function Where(const predicate: TPredicate<T>): IEnumerable<T>;
    function Skip(count: Integer): IEnumerable<T>;
    function SkipWhile(const predicate: TPredicate<T>): IEnumerable<T>; overload;
    function SkipWhile(const predicate: TFunc<T, Integer, Boolean>): IEnumerable<T>; overload;
    function Take(count: Integer): IEnumerable<T>;
    function TakeWhile(const predicate: TPredicate<T>): IEnumerable<T>; overload;
    function TakeWhile(const predicate: TFunc<T, Integer, Boolean>): IEnumerable<T>; overload;
    function Concat(const collection: IEnumerable<T>): IEnumerable<T>;
    function Reversed: IEnumerable<T>;
    procedure ForEach(const action: TAction<T>); overload;
    procedure ForEach(const action: TActionProc<T>); overload;
    procedure ForEach(const action: TActionMethod<T>); overload;
    function EqualsTo(const collection: IEnumerable<T>): Boolean; overload;
    function EqualsTo(const collection: IEnumerable<T>; const comparer: IEqualityComparer<T>): Boolean; overload;
    function ToArray: TArray<T>;
    function ToList: IList<T>;
    function ToSet: ISet<T>;
    function GetCount: Integer;
    function GetIsEmpty: Boolean;
    property Count: Integer read GetCount;
    property IsEmpty: Boolean read GetIsEmpty;
  end;

That’s a lot of cool stuff, right?  Many of the methods here are pretty clear -- it's pretty obvious what the Min and Max methods do. But some of the others are a bit trickier.  You can get all of the items.  You can get the First and Last items.  You can get the first x number of items.  You can get the items back out as a List, an Array, or a Set.  And most powerfully, you can get any particular group of the items out using a predicate.

Predicates

What is a predicate?  A predicate is an anonymous function (or any function, really, but in our case it is an anonymous function) that takes a single const parameter of the type in question and returns True or False.

 TPredicate<T> = reference to function(const value: T): Boolean;

It is used with some of the methods of IEnumerable<T> to answer this simple question:  In or out?  If the predicate is true, then the individual item is “in”, or included.  If it is “out”, then the predicate will return False.  So if you want to get all the items in a list strings that contain the letter ‘z’, then you can do the following:

function ContainsLetterZ: IEnumerable<string>;
var
  List: IList<string>;
begin
  List := TCollections.CreateList<string>;
  List.AddRange(['zoo', 'park', 'city', 'town', 'museum', 'jazz festival']);

  Result := List.Where(function(const aString: string): Boolean
                     begin
                       Result := Pos('z', aString) > 0;
                     end);
end;

The above uses the Where method to determine items that should be returned as part of a new IEnumerable<string>.  The above is saying “return to me an enumerable item where all the strings in the list have ‘z’ in them.”

You can do similar things with the TakeWhile method, which returns items from the start of the list as long as the predicate is True, and stops once the predicate is False.   You can determine if a given container has or doesn’t have a given element.  You can Skip over a given number of elements and take the rest.  You can use a predicate to SkipWhile a certain thing is true, and then return the rest once the predicate returns True  Basically, once you have a reference to a collection or any IEnumerable<T> instance, you can get out of it pretty much anything you want.

Something to consider:  If you have a nicely composed class that includes a private IList<T> which gets exposed through proxy methods, then you might want to expose access to the items via a property of type IEnumerable<T> instead of exposing the actual list itself.

And the real power gets unleashed with the very powerful ForEach methods.  Folks have always pined for the lovely ForEach since the days of Borland Pascal’s old TCollection object, and now it’s back in full force, leveraging the power of anonymous methods.  Thus, you can have a collection and do what you please with the items.  Here is a simple example of just outputting them to the console window, but you can have your TAction<T> do anything at all that you like for each (sorry) element in the container.

procedure SimpleForEachDemo;
var
  List: IList<integer>;
  Action: TAction<integer>;
  i: Integer;
begin
  Action := procedure(const aInt: integer) begin Writeln(Format('This number is: %d', [aInt])); end;

  List := TCollections.CreateList<integer>;
  for i := 1 to 10 do
  begin
    List.Add(Random(100));
  end;

  List.ForEach(Action);
end;

So, basically you have a lot of untapped power there in Spring.Collections.pas, eh?  The use of IEnumerable<T> and predicates ought to transform your code and change the way you look for things in lists and collections.  If you aren’t using these powerful tools yet, I strongly recommend that you add them to your tool chest.

I’ve written a pretty thorough example application for IEnumerable<T> and put it up on BitBucket.  I also have some pretty simple examples showing predicates at work.

Remember, the Delphi Spring Framework is more than a Dependency Injection container.  And there’s more to it than the container classes as well.  I strongly recommend that you start treating Spring4D like part of the Delphi Runtime Library.  Heck, I wish Embarcadero would do just that.

Comments (17) -

1/6/2013 10:02:24 PM #

Very comprehensive! Thanks for the article, Nick!

Paul United States |

1/6/2013 10:20:49 PM #

Is Spring still being worked on? I ask because the last release was March 2012 (at least as far as the base site is concerned) and the last posting at Spring4D is dated July.

I was looking for an open source project to contribute to, but when I looked at spring, it didn't seem like it is active at all.

Am I just looking in the wrong place?

Doug Johnson United States |

1/6/2013 10:35:41 PM #

Doug -

Yes, Spring is being actively worked on.  

The best place to look is code checkins:

code.google.com/.../list

nick United States |

1/7/2013 2:20:01 AM #

A word of caution about using Spring4D collections if you really care for binary size and don't need all the let's call them extension methods of IEnumerable<T>: your binary size may explode (and I am talking about megabytes if you have many different types for T here) because every generic specialization pulls in all the methods from the interface if they are used or not.

Stefan Glienke Germany |

1/7/2013 5:06:58 AM #

Yep, and since all those methods are referred in an interface, the compiler won't smart-link any of them. So you always get the full collection of methods compiled into your binary for every type, as the compiler won't de-virtualize nor fold binary-identical implementations.

Eric France |

1/7/2013 8:25:34 AM #

I made some experiments with record types specifying the "extension methods" wrapping the interface which just has the least required methods (like  Add, Delete and so on) in the past (see DSharp.Collections.Extensions.pas) which works pretty well as any non used method gets smart linked out. The only disadvantage of course is that you have to specify the extension methods on each type (Enumerable<T>, List<T>, ...) and have to make optimizations outside of the class itself (like things that can be handled more performant in a list with direct index access than in a pure forward only enumerable).

Stefan Glienke Germany |

1/9/2013 12:50:58 AM #

@Stefan, Yes, the binary size is so important that we need to try to optimize the design. Using record is an alternative with sacrificing some usability.

Let's have more discussion on it when I have internet in days...

Baoquan Zuo United States |

1/7/2013 5:39:21 PM #

So all that work to turn Pascal into Python? Wink

list = [1,6,2,9,54,3,2,7,9,1]
print(max(list))

or sort and grab the last one:

list = [1,6,2,9,54,3,2,7,9,1]
list.sort()
print(list[-1:][0])

and if you really want to be amazed we can find the min/max of tuples like this:

list = [(1,3),(2,5),(2,4),(7,5)]
zip(*alist)

which produces
[(1, 2, 2, 7), (3, 5, 4, 5)]

and then....

map(max, zip(*alist))
[7, 5]
map(min, zip(*alist))
[1, 3]

Smile

Nice to see you bringing modern conveniences to Delphi. From what I can see this Spring4d looks very impressive. Wish this around circa 1999/2000 when I was developing my most complex Delphi programs. I was a decades-long Turbo Pascal/Modula 2/Borland Pascal/Delphi programmer but in the last few months python has finally stolen my heart (helped along by Linux support). I sometimes call it "my new Delphi", which only long-time Delphi developers can understand. Smile

Oh, and the list in the "Predicates" example?

Clearest way:

hodgeList = ['zoo', 'park', 'city', 'town', 'museum', 'jazz festival']
newList=[]
for word in hodgeList:
  if 'z' in word:
    newList.append(word)

print(newList)
['zoo', 'jazz festival']

Shortest way, no lambda functions (maybe it's all the structured Pascal, but inline functions just feel morally wrong to me Smile ):

hodgeList = ['zoo', 'park', 'city', 'town', 'museum', 'jazz festival']
hl = [word for word in hodgeList if 'z' in word]

Print(hl)
['zoo', 'jazz festival']





Joseph United States |

1/8/2013 9:34:18 AM #


Yeah, python has all kind of syntactic sugar sprinkled all around.

But... can you create a self-contained executable program with a nice GUI out of the box?

Leonardo Herrera Chile |

1/28/2013 12:40:13 AM #

Python comes with the TKinter GUI kit built-in, but since there really isn't a "box", one can use wxPython, GTK+, or especially Qt. cx_Freeze will give you an executable and comes bundled with the IDE I use (which also links with QtDesigner for designing Qt GUIs). And it'll do it in half the time of a non-scripting language too. Wink

Joseph United States |

1/7/2013 6:39:00 PM #

Hmm... so is this any different than Alex Ciobanu's Collections library?

Mason Wheeler United States |

1/7/2013 8:55:51 PM #

Mason --

Well, it's supported, for one thing.  I believe Alex isn't supporting his collections anymore...?  Or am I mis-informed...?

I was really sad when he left the Delphi team.

Nick

nick United States |

1/8/2013 9:41:46 AM #

He stopped working on DeHL Collections, then started working on something called Collections 2.0. But it has been an exact year since he posted something on his blog, so maybe moving to Spring.Collections is a good idea.

Leonardo Herrera Chile |

1/10/2013 10:33:31 AM #

Hi There!

Yes, I stopped. The reason is easy - the IDE is still too unstable for Generics at that level of inheritance. But the most important thing is the binary size. It's just impractical to use unfortunately.

To be honest I recommend you use Generics.Collections standard unit... As that is very simple implementation and will not carry over tons of baggage like DeHL or Collections 2.0 or even Spring4D (sorry Nick).

Until the complier and IDE drastically improve the use of complicated generics is a baaaaad idea. I would not risk jeopardising you project
with future untraceable ICEs...

Alex Romania |

1/10/2013 1:25:15 PM #

Thanks for stopping by, Alex -- I hope to see more Delphi code from you in the future!  Smile

nick United States |

1/7/2013 9:31:30 PM #

I I might be mistaken, but IIRC he said that it's on hold until the compiler team is able to implement proper generics folding.  Everything Steven said about insane bloat is absolutely true, and also absolutely unnecessary if someone would just put a few days of hard work into the linker.  Unfortunately, that doesn't seem to be very high on anyone's priorities at Emb right now...

Mason Wheeler United States |

1/10/2013 7:49:33 PM #

Ugh.  That was supposed to be a reply to Nick's reply, above, and I totally spelled "Stefan" wrong.  I can blame the first on this blog engine not playing nicely with my phone's browser, but the second is just a simple case of cerebral flatulence on my part. Frown

Mason Wheeler United States |

Comments are closed

My Book

A Pithy Quote for You

"When things go wrong in your command, start searching for the reason in increasingly larger concentric circles around your own desk."    –  General Bruce C. Clarke

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.

Month List