CodeGear, Please Fix the Anonymous Method Assymetry
As I noted in my previous post, anonymous methods is a big new feature in Delphi 2009 for the Win32 platform. While “closures” is natural and much appreciated in other languages, most notably Ruby, the Delphi community is still a bit reluctant and hesitant. Segunda Feira put words on it in his post on the subject.
I am still not convinced that this convenience is worth getting all that excited about – It has not enabled anything that was previously impossible, nor even especially difficult either to implement or to understand
anonymous methods should be used only when you absolutely have to, and so far I have not yet seen an example where anonymous methods are the only way to achieve anything
I agree that more often than not a problem can be solved with equal elegance using a pure object orientated approach, but there are situations where anonymous methods may actually be the better alternative. One situation that comes to mind is the setting up of test fixtures.
Say, for instance, that we want to test the event firing mechanism of a component. An easy way to set this up could be like the following.
procedure TestSomeComponent.TestOnChange; var SUT: TSomeComponent; OnChangeFired: Boolean; begin OnChangeFired := False; // Set up the fixture SUT := CreateTheComponentToTest; SUT.OnChange := procedure(ASender: TObject) begin OnChangeFired := True; CheckSame(SUT, ASender, 'The Sender event argument should be set'); end; // Perform operation SUT.Text := 'Some text'; // Check the result CheckTrue(OnChangeFired, 'Setting the Text property should fire the OnChange event'); end;
The above code checks that the component’s OnChange event is fired when the Text property is set, and that the component is set as the Sender reference to the event handler. Except for being more compact, the biggest advantage to using anonymous methods in this scenario is that we avoid the obscure test smell and that we don’t have to clutter our test class with an instance variable (like FOnChangeFired) to identify if the event was really fired or not.
The only problem is: it doesn’t work. Why? Well, because the OnChange property is most likely of an instance method reference type (i.e. TNotifyEvent), meaning it doesn’t accept a references to an anonymous method even if it has the same signature.
type TNotifyEvent = procedure(ASender: TObject) <strong>of object</strong>;
For our code to compile we need to redeclare TNotifyEvent and remove the “of object” keywords and instead use the method reference type.
type TNotifyEvent = <strong>reference to</strong> procedure(ASender: TObject);
But of course, that’s not an option. It would mean that the traditional way of setting up an event handler (by using an instance method) would not work.
I see a definite problem with how the Delphi language explicitly forces you to distinguish between instance methods (and class methods for that matter) and anonymous method references, even though they share the same signature.
This is most unfortunate since I feel that the situations where we’d like to support both kinds of event handlers are quite common. And with the current semantics we have to use code duplication in order to achieve that. Like the two Synchronize methods of the built in TThread class.
In Delphi 2009 an overloaded variant of the TThread.Synchronize method was introduced, one that make use of anonymous methods. Here are snippets from that code:
type TThreadMethod = procedure of object; TThreadProcedure = reference to procedure; ... class TThread = class … procedure Synchronize(AMethod: TThreadMethod); overload; procedure Synchronize(AThreadProc: TThreadProcedure); overload; ... end;
The two methods have almost identical implementations. The only real difference is the type of the argument.
procedure TThread.Synchronize(AMethod: TThreadMethod); begin FSynchronize.FThread := Self; FSynchronize.FSynchronizeException := nil; FSynchronize.FMethod := AMethod; FSynchronize.FProcedure := nil; Synchronize(@FSynchronize); end; procedure TThread.Synchronize(AThreadProc: TThreadProcedure); begin FSynchronize.FThread := Self; FSynchronize.FSynchronizeException := nil; FSynchronize.FMethod := nil; FSynchronize.FProcedure := AThreadProc; Synchronize(@FSynchronize); end;
I may be overly sensitive, but code like that really disturbs me. Unfortunately it gets worse. If we follow the execution chain down into the Synchronize class method that is invoked by the two, we find this.
class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord; QueueEvent: Boolean = False); var SyncProc: TSyncProc; SyncProcPtr: PSyncProc; begin if GetCurrentThreadID = MainThreadID then begin if Assigned(ASyncRec.FMethod) then ASyncRec.FMethod() else if Assigned(ASyncRec.FProcedure) then ASyncRec.FProcedure(); end else … end;
It would be a lot nicer if the two reference types were joined under a common reference type. And I can’t see why it couldn’t be done. When I look at the “of object” keywords I get a feeling that the language is leaking compiler implementation through the language interface; information that is indifferent to the developer. What matters from a callers’ perspective is the method signature, not whether the method has a self pointer or not.
I hope CodeGear recognizes this problem and find a way to clean this assymetry from the language. Anonymous methods would be so much more useful if they do.