Thursday, September 13, 2007

A Generic Playground, an intro to parameterized types in Delphi for .NET

The other day, I presented an introduction to the new parameterized types language feature in the latest release of Delphi for .NET at the Developer Days event. First of all, I'm not what one would call a consummate expert in the realm of language generics. In fact a lot of it just makes my head hurt. For real hard-core, on the edge, looks at the kinds of things you can do with generics, you should be looking here or here. I'm sure you may recognize those guys that run those blogs.

So given that disclaimer, I'm going to start presenting some topics on using the new parameterized types in the Delphi language. Keep in mind that for this release this only applies to Delphi for .NET. Check out the roadmap for some guidance on when this will be coming to the Win32 side of the house. I'm going to approach this from the perspective that as a Delphi programmer, you've never used generics or parameterized type. You may not even know what they are, how to use them, and why. That's ok... Me neither. :-)

In many ways,"generic" programming takes the code reuse tenet of Object Oriented Programming (OOP) to a whole new level. When OOP was becoming more prominent throughout the '80s, a key advantage to the concept was this notion of being able to reuse and extend already written code in ways not considered by the original author. Instead of reusing code via inheritance and polymorphism (although parameterized types strongly leverage OOP), it is handled by separating the type of the data being operated on from the algorithm.

Suppose you've written a killer new sorting algorithm. Now you want to use that algorithm to sort a variety of different types of items. In one case you want to sort a simple list of integers. Other times you may want to sort some complex data structure based on a collating sequence unique to that data. Without "generics" you probably would have written this algorithm using traditional OOP polymorphism. So when you wanted to use your killer sort function, you'd have to create a unique descendant of the base class that implements the algorithm, override a few methods, and finally create an instance of this new type to use it.

Generics allow you to, in many cases, forgo the sometimes tedious, often mind-numbing, task of creating yet another descendant class just to sort that new data structure you just defined. With a parameterized type, you write your algorithm as if you were writing it for a specific data-type (yes, I know there is a little more you have to consider, this is the basic gist of it). So let's start with something simple.

So I find myself constantly needing to reverse the items in an array. I have this awesome algorithm that I keep writing over and over. Sometimes I need to reverse an array of integers and other times it's strings. So, how could I use parameterized types to only write this killer algorithm only once and then tell the compiler that I need a version to work with integers, or strings, or some other structure only when needed. Here's what I came up with:

type
TArrayReverser<T> = class
procedure ReverseIt(var A: array of T);
end;

procedure TArrayReverser<T>.ReverseIt(var A: array of T);
var
I: Integer;
E: T;
begin
for I := Low(A) to High(A) div 2 do
begin
E := A[I];
A[I] := A[High(A) - I];
A[High(A) - I] := E;
end;
end;


Eat your heart out, Mr. Knuth ;-). Now I can actually use the above parameterized type anyplace that I want to have the order of an array reversed, regardless of what the element types and sizes are. I don't have to create a descendant class and override some methods. I just instantiate the above type using the type of the array elements as the parameter. For example:

var
I: Integer;
IntegerArray: array of Integer;
IntegerArrayReverser: TArrayReverser<Integer>;

begin
SetLength(IntegerArray, 10);
for I := Low(IntegerArray) to High(IntegerArray) do
IntegerArray[I] := I * 10;
IntegerArrayReverser := TArrayReverser<Integer>.Create;
IntegerArrayReverser.ReverseIt(IntegerArray);
for I := Low(IntegerArray) to High(IntegerArray) do
Writeln('IntegerArray[', I, '] = ', IntegerArray[I]);
end;


So there is a quick intro to using parameterized types in Delphi for .NET. I'm sure you can see some of the interesting things you can do with this new found power. Another interesting fact about using generics is that in many cases the code is actually more compact and faster than if you'd tried to make a general purpose class using traditional OOP techniques. This is because the body of the ReverseIt procedure is compiled (at runtime in .NET, at compile-time for Win32) as if 'T' were actually declared as an 'Integer'. We could take the class I wrote above and use it to reverse an array of TPoint or TRect structures. Any type you declare or an intrinsic language type can be used to create a concrete version of TArrayReverser. If you have any questions about this pose, please post them in a comment.

12 comments:

  1. A compact Example and fine satire.

    ReplyDelete
  2. You know thats just fabulous, not keep it the hell out of ALL the other versions of Delphi. This is pretty much gonna screw up most everything. What are you guys trying to do, turn pascal into basic?

    Pascal is a *strictly typed language* and it was done that way for good reasons.

    Why not just go the whole route, drop types all together and just have everything determined by the compilers best guess?

    I should slap you upside the head for doing such an stupid thing to a language that was great until people like you came along.

    ReplyDelete
  3. Ah, no I understand. Generics enable us to write neat, tidy code in one place that we can then use using ugly and cumbersome invocation mechanisms throughout our code.

    If you are writing "ReverseIntegerArray()" over and over again then you need to look at your software development process and management tools.

    You should of course have written this once, long ago, and now simply be using it all the time by simply calling:

    ReverseIntegerArray( someIntArray );

    If your existing practices didn't allow this sort of simple, elegant and efficient re-use then I humbly suggest that you will quickly find yourself in an even deeper mess with generics.

    r maybe it was just a bad example - funny how so many generics examples are bad examples.....

    ;)

    ReplyDelete
  4. I think the syntax should be
    function InList<T>(Value: T; const Arr: Array Of T): Boolean;
    :-)

    Uli.

    ReplyDelete
  5. C Johnson:
    > I doubt something like this would work currently

    Of course you can.

    In the current version you have to wrap the (possibly static) method inside a class. The syntax would be:

    function TUtility.InList(Value : T; const Arr : Array Of T) : Boolean;

    ReplyDelete
  6. William,

    What specifically are you complaining about? About how to generics compromise the strict typing of Object Pascal? In fact, parameterized types *increase* the type safety of the language. Can you cite some examples where this type safety is compromised?

    Thanks,
    Allen.

    ReplyDelete
  7. Fantastic.

    I have a question:
    Am I right that compiled code in Native mode will be bigger? And is it compiled smartly, meaning only the types used in code will be generated?

    ReplyDelete
  8. K A,
    Unless a parameterized type is actually instantiated and referenced, code for it will never be generated nor linked in. It will also be smart enough to know that anyplace it generates a, for instance, "TMyType<Integer>" instantiation, only one copy will be linked in.
    Parameterized types *can* lead to larger code size, however in many cases it actually can reduce the overall size.
    Allen.

    ReplyDelete
  9. Thanks for the info, Allen. Nice example. Btw, by mentioning Barry and ChuckJ this means, of course, that Barry will start to blog more often and ChuckJ will return ASAP, isn't? Well, now you're bound by 'future looking statements' so you're obliged to accomplish them... ;-)

    ReplyDelete
  10. William: "I should slap you upside the head for doing such an stupid thing to a language that was great until people like you came along."

    Or perhaps someone should slap you for being so rude... Given how long Allen's been at Borland/CodeGear mind, I take it you think the rot set in when Turbo Pascal gained objects.

    ReplyDelete
  11. Allen, do you consider to implement mixins in Tiburon time frame and also to provide the possibility that those mixins to be parameterized? (ie. to implement a parameterized/generic mixin)

    ReplyDelete
  12. Great!
    Once this is through Im sure some enterprising individual will reimplement the STL for delphi, bringing up Delphi development up a notch to rub shoulders with C++

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.