Home > Delphi, opinion, programming, software development, testing > CodeGear, Please Fix the Anonymous Method Assymetry

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.

Cheers!

Be Sociable, Share!
  1. June 3rd, 2009 at 22:26 | #1

    The reason that method pointers and method references can’t be joined into a third common type is that method pointers do not require garbage collection, while method references do (and implement it via reference counting).

    Method references are the union type; values of a method pointer type can be converted into a method reference type. The reverse isn’t possible because of the GC issue.

    The reason VCL events can’t be changed to use method references is backward compatibility; far too much legacy code uses the method-cracking functionality of a cast to TMethod, or rely on method pointers being blittable values that don’t require initialization or finalization, whereas managed types like strings, dynamic arrays, interface references – and method references – do.

    Short note on why method references need GC: multiple anonymous methods may be written which capture state from their respective environments, and all stored into locations of a single method reference type. Method references, in other words, are polymorphic, while the implementation of captured state requires storage of variable size.

    That combination of features means that the captured state storage needs to be on the heap, as unbounded variable-sized values can’t be polymorphically stored in locations of a single type. That in turn means some kind of lifetime management must occur, so that the heap allocation can safely be retired when all outstanding references to the captured state, via method references, have gone out of scope.

    There could be two other implementations of anonymous methods / method references which would obviate this limitation, and possibly permit method references to be stored in method pointers:

    1) Prohibit variable capture by anonymous methods. This would limit their usefulness so much as to make them a questionable language addition.

    2) Require manual memory management for captured state. However, since one of the raison d’etre’s of anonymous methods is that they permit creation of code, probably capturing locally-scoped state, in one module, and execution (via callback, essentially) from a completely independent module, the lifetime semantics are highly likely to require some kind of negotiation protocol – like, say, reference counts. Rather than develop such a protocol convention, and suggest that everyone follow it, it makes far mores sense if the compiler automates what needs to be done automatically.

    So, yes, I’d like to make the VCL use method references by default, but it can’t happen for backward compatibility reasons. And as for a unifying type – method references are the unifying type.

  2. June 3rd, 2009 at 22:28 | #2

    Oh, and I forgot to mention the proper answer to all our problems: garbage collection. However, it introduces another set of problems, not least of which is the community mindset, quite apart from compatibility.

  3. June 4th, 2009 at 10:48 | #3

    Thank you for taking your time to shed some light on this subject.
    The reasons seems valid enough although I’m sure the issues can be addressed so that the compiler can handle all necessities behind the scene (like figuring out wether the reference needs to be GC:ed or not), possibly spawning lots of coding horrors in the code base but at least being abstract to the users of Delphi.
    But then again, my experience in developing compilers and languages is – well – limited.

    I wasn’t aware that method references are able to hold method pointers. That fact mitigates this particular problem somewhat and I guess that’ll have to be the topic of my next post.

  4. June 4th, 2009 at 11:00 | #4

    Oh yes! I’ve gotten used to the sloppy but convenient programming style that comes with a garbage collecting language and are now having a slight trouble readjusting.

    I’d really love to see Delphi empowered with GC, preferably like it’s done in the D Programming Language: Managing Object Lifetime in D, with both implicit and explicit lifetime handling.

  1. June 5th, 2009 at 11:59 | #1