Tuesday, September 27, 2016
Set in my ways
Friday, March 26, 2010
This is the last day…
In this office. I’ve been in the same physical office for nearly 15 years. After years of accumulation, it now looks positively barren. Beginning next Monday, March 29th, 2010, I’ll be in a new building, new location, and new office. The good thing is that the new place is a mere stone’s throw from the current one. It will be great to leave all the Borland ghosts behind.
Wednesday, November 21, 2007
When code lies - A better solution
Many of you wretched and guffawed at my silly post about Stupid Enumerator Tricks. You know what? I agree. It was a hideous misuse of a feature. One comment from Joe White that stood out was the notion that the code was lying. He is right. I was using a a side-effect as the primary feature, not what the code was actually saying. However, from casually observing the code in question, it could easily be misconstrued. The code was lying.
How would the maintainer of this code figure out the intent a year or more from now? What if I were the maintainer? How many times have you looked at a chunk of code you wrote a long time ago and about all you remember about it was "how utterly awesome and clever you were?" But now, you don't have the faintest idea what it does and how it works. Sure, you could have put comments in, but what if they were wrong and misleading? (this is where I like to say that no comment is far better than a wrong or bad comment). After staring at the code for what seems like hours... you suddenly have a forehead-slapping moment and exclaim, "What was I thinking!!"
But is there a better method of handling the automatic cleanup of this task? Another commenter, Jolyon Smith suggested the use of interfaces as a way to automatically manage the lifetime of an object. I, too, have used this little trick as well. Let's look at my TTask, TTask<T> and TReplicableTask implementation. Here's the declaration of the classes and the interfaces. Yes, I'm using Win32 generics... because, well... they work (for the most part) on my machine :).
type
TFunctionEvent<T> = function (Sender: TObject): T of object;
ITask = interface
procedure Wait;
procedure Cancel;
function GetIsComplete: Boolean;
property IsComplete: Boolean read GetIsComplete;
end;
ITask<T> = interface(ITask)
function GetValue: T;
property Value: T read GetValue;
end;
EOperationCanceled = class(Exception);
TTask = class(TInterfacedObject, ITask)
private
FDoneEvents: THandleObjectArray;
FCanceled: Boolean;
FException: TObject;
protected
FEvent: TNotifyEvent;
function GetIsComplete: Boolean;
procedure WorkEvent(Sender: TObject);
procedure QueueEvents(Sender: TObject); virtual;
procedure Wait;
procedure Cancel;
property IsComplete: Boolean read GetIsComplete;
public
constructor Create(Sender: TObject; Event: TNotifyEvent);
destructor Destroy; override;
procedure CheckCanceled;
end;
TTask<T> = class(TTask, ITask<T>)
private
FEvent: TFunctionEvent<T>;
FResult: T;
procedure RunEvent(Sender: TObject);
function GetValue: T;
property Value: T read GetValue;
public
constructor Create(Sender: TObject; Event: TFunctionEvent<T>);
end;
TReplicableTask = class(TTask)
protected
procedure QueueEvents(Sender: TObject); override;
end;
So now the usage is like this:
procedure TForm1.Button1Click(Sender: TObject);
var
IntTask: ITask<Integer>;
begin
IntTask := TTask<Integer>.Create(Self, CalcResult);
Caption := Format('Result = %d', [IntTask.Value]);
end;
function TForm1.CalcResult(Sender: TObject): Integer;
var
I: Integer;
begin
Result := 1;
for I := 2 to 9 do
Result := Result * I;
end;
Much cleaner, don't you think? And remember, even if the task isn't complete yet, the call to IntTask.Value does an implicit wait until the task has obtained the value. By making the methods private or protected (or, even better, strict private and protected) on the TTask and TTask<T> classes, you can be more assured that someone doesn't decide to use the object directly. You must obtain the interface in order to use the class instance.
Update: I just updated this Code Central entry with a new Parallel.pas unit that demonstrates some of the above, along with a new TTask, TReplicableTask, and TNestedReplicableTask objects along with requisite interfaces. A compiled version of the Life demo was also included per some requests. Finally, the Parallel.pas unit demonstrates a new technique for calling nested procedures without having to litter your code with a bunch of assembler blocks. I'll post another blog entry describing it.
Thursday, September 13, 2007
A Generic Playground, an intro to parameterized types in Delphi for .NET
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.