Sunday, April 17, 2011

Yield return and Delphi

Well, some may know that Sergey Antonov already came up with some implementation on the yield functionality for Delphi in 2007. It was pretty impressive but for my taste it involved to much asm stuff and it was before Delphi had generics.

So what if we could do something like this?

for i in Power(2, 10) do
begin
  Writeln(i);
end;

Well, you can! All you need is define the function as follows:

function Power(ANumber, AExponent: Integer): IEnumerable<Integer>;
begin
  Result := TDelegateEnumerable<Integer>.Create(
    procedure
    var
      i, k: Integer;
      Result: Yield<Integer>;
    begin
      k := 1;
      for i := 1 to AExponent do
      begin
        k := k * ANumber;
        Result := k;
      end;
    end);
end;

Looks pretty much simple and straight forward, eh?

So what happens there? Well basically it creates some TEnumerable<T> descendant which takes a TProc as parameter and executes it when GetEnumerator is called. Well but thats only half the truth. The real magic happens when you assign a value to that Yield<Integer> variable. And that's basically a record with some implicit operator overload which makes the worker thread or fiber (for now you can only set that with a compiler switch in the Collections.Yield.pas) continue and make the Enumerator do his MoveNext and provide the value for the GetCurrent.

I also implemented support for Alex Ciobanu's awesome collection library (you can also set that up with a compiler switch in the Collections.Yield.pas). Then you can do something funny like this (provided that you set the result type of your function to IEnexCollection<T>):

for i in Power(2, 10).Reversed do
begin
  Writeln(i);
end;

Ok, you might say, I can do all that with getting my values and putting them into some list and I can easily go through it with any loop I want. True, but then you will either have to wait until your calculation and processing is done and the list is generated. And also you have to carry around the list and the elements in it. What if you just want to set up some parameters and calculate the result later? Well with that implementation you can since the enumeration procedure is just called when GetEnumerator is called.

So, pretty cool, no? What do you think?

P.S.: The full source code is available on google code as always.

7 comments:

  1. What do I think? I think that is really cool.

    ReplyDelete
  2. Similar stuff :)
    http://www.delphi-forum.de/viewtopic.php?t=94597

    ReplyDelete
  3. Oh damn, now you got me, no link to your original implementation.
    Basically I went a step further eliminating most assembler code and adding the option to rely on threads instead of fibers.

    But I'm not trying to steal your credits anyway, lots of your work inspired me. :D

    ReplyDelete
  4. I have successfully implemented your solution in a test project and am pleased with the results.

    There is something odd, however; The IDE complains that TDelegateEnumerable does not exist. Despite this the project compiles and runs perfectly under all configurations and, perhaps odder still the code completion lists the class as I type.

    Any thoughts?

    ReplyDelete
    Replies
    1. Hi Hugh,

      if with "The IDE complains" you mean Error Insight just ignore it. This feature is so broken with generics that it is better to turn it off instead of wondering about all the red squiggly lines.

      Also please be aware that I recently changed the name to TYieldEnumerable so its use is more clear.

      Delete
    2. Ok, I am new to XE2 so was not aware of that.

      The solution works very sweetly.

      Delete