Sunday, September 25, 2011

AOP and duck typing in Delphi

AOP - something that fascinated me from the very first moment when my coworker told me about PostSharp. Unfortunately something that was almost impossible in Delphi without the proper knowledge of assembler and all the internal low level stuff. However even with Delphi 7 it was possible to a certain degree to intercept virtual methods using MeAOP.

With the recent additions (namely enhanced RTTI) it became very easy to introduce this feature in a more safe way. However this is nothing compared to a framework like PostSharp which is able to enhance almost everything using IL weaving. Currently you can intercept any virtual method that has RTTI (by default any public/published method). It is very similar to what DynamicProxy does. Delphi XE introduced the TVirtualMethodInterceptor that made it possible to intercept the virtual method calls of an existing object. This is done by creating a new VMT at runtime and attaching it to the object. I went a step further and made it possible to patch any existing class VMT so that any object of this class (and every derived class if you wish) can be intercepted. You can add any aspect to any method you like (if it is virtual and has RTTI). There is currently one built-in aspect for logging which logs entering and leaving the method, all parameters and the result if there is one. You can find a simple demonstration on how to instrument two virtual methods to show logging in the SVN repository.

Did you ever face the problem of having a dependency in your code that you could not remove so simply? Like having that class in your code that is passed around but not being part of your source code so you cannot mock it out? Well you could inherit that class and implement some interface so you just have the dependency on that interface. Yes if that class is not sealed (did you know you can use that keyword in Delphi?). To be honest I haven't seen anyone using this yet (could be because I mostly work with sourcecode written in Delphi 7). But let's say you have a class that is sealed and you want to remove the dependency. You could write an abstract wrapper class and then inherit from it to remove the dependency from your code. Sounds like a lot of work, right?  How about duck typing?





When you look at what duck typing means this is very similar to as if the class implements a specific interface. "Walks like a duck and swims like a duck and quacks like a duck" basically means it got these 3 methods. Like implementing IDuck with these 3 methods. But duck typing does not require the class to implement this interface. Prism got duck typing just recently and DSharp now got it too! (only for XE2 yet, sorry) Of course no compile time support but something similar to what the RemObject guys call soft interfaces and dynamic duck typing mode.

Here is the program from the wikipedia page in Delphi!

program DuckTypingSample;

{$APPTYPE CONSOLE}

uses
  DSharp.Core.DuckTyping;

type
  IDuck = interface(IInvokable)
    procedure Quack;
    procedure Feathers;
  end;

  TDuck = class(TInterfacedObject, IDuck)
  public
    procedure Quack;
    procedure Feathers;
  end;

  TPerson = class
  public
    procedure Quack;
    procedure Feathers;
    procedure Name;
  end;

{ TDuck }

procedure TDuck.Feathers;
begin
  Writeln('The duck has white and gray feathers.');
end;

procedure TDuck.Quack;
begin
  Writeln('Quaaaaaack!');
end;

{ TPerson }

procedure TPerson.Feathers;
begin
  Writeln('The person takes a feather from the ground and shows it.');
end;

procedure TPerson.Name;
begin
  Writeln('John Smith');
end;

procedure TPerson.Quack;
begin
  Writeln('The person imitates a duck.');
end;

procedure InTheForest(duck: IDuck);
begin
  duck.quack();
  duck.feathers();
end;

var
  donald: IDuck;
  john: TPerson;
begin
  donald := TDuck.Create;
  john := TPerson.Create;
  InTheForest(donald);
  InTheForest(Duck<IDuck>(john));
  john.Free;
  Readln;
end.

Notice how this is different from dynamic typing you could do with invokable variants. Like the variants approach the evaluation is done at runtime. But in this case you have more type safety. Instead of just passing a variant to the InTheForest procedure and not having any type safety (you could also write duck.Bark() and compiler would not complain) you only can call methods of the IDuck interface. If the wrapped object does not have the required method (also matching parameters) it throws the not implemented exception.

What is it good for? This can open up certain parts of your application to unit testing and mocking that you could not do before because you had lots of static dependencies that could not be mocked. And of course to show that Delphi is catching up. ;)

10 comments:

  1. Good stuff as always!

    ReplyDelete
  2. Hi.

    Duck < IDuck > (john));

    It is a bit confusing sample on using language keyword, not Duck Type.
    First duck is a language keyword, not Duck Type.

    ReplyDelete
  3. @Sergey: Yes in this example it might be confusing because of the IDuck type. But I choose the "language keyword" (which is internally a record using operator overloading to get around the lack of generic routines) to be duck because also Prism did it that way (you also notice it because it got no T or I at the beginning).

    ReplyDelete
  4. @Stefan.

    So, is duck something like this?

    duck < T:interface > = packed record
    operator explicit(obj:Tobject):T;
    end;

    So, do your version supports of composing IDuck methods, not only from objects methods(
    instance offset 0), but from object interfaces methods(i.e. delegated to class or interface property), so you have to write correction stub for instance param of methods?

    :)

    ReplyDelete
  5. But "ducking" composite from different interfaces methods brings a problem of different copy semantics of such interfaces.

    IDucking=interface
    procedure A;
    procedure B;
    end;

    IA=interface
    procedure A;
    end;

    IB=interface
    procedure B;
    end;

    TDuckingStuff=class(TinterfacedObject,IA,IB)
    property DIA:IIA ...implements IA (counting)
    property DIB:IIB ...implements IB (not counting)
    end;

    So,to get a problem just

    duck(instance of TDuckingStuff)

    :)

    ReplyDelete
  6. That doesn't work because TDuckingStuff does not have procedure A and B so it throws ENotImplemented when calling either of these.

    So the answer to the initial question (does it support composition of methods implemented by different interfaces) is no.

    ReplyDelete
  7. Where does ENotImplemented occur?

    TDuckingStuff has "transitive" procedures A and B.
    But to call we need
    IA(DuckingStuff).A()
    IB(DuckingStuff).B()

    I think it is just Delphi compiler boundedness.
    I don't know why it isn't allowed to write

    DuckingStuff.A()
    DuckingStuff.B()

    ReplyDelete
  8. I raise ENotImplemented whenever you call a method that does not exist in the duck typed object since there is no "type safety check" when you do the "cast". As mentioned in the post it's similar to what oxygen does with soft interfaces and the duck typing mode dynamic.

    You cannot call any method that is technically not implemented by the class itself (but delegated to a member) since Delphi is strongly typed. Being able to do would be against that strong type safety.

    What my "duck typing" does is basically create a dynamic wrapper around the duck typed object and expose the interface.

    However you could extend that wrapper to be able to call methods of interfaces that are implemented by the original.

    ReplyDelete
  9. @Stefan
    Please, explain where do you see the violation of type safety.
    Can you write type checking rules in translation to lambda or object calculus?
    if the object implements interface(s), then that object can masquerade as interface(s), i.e. object is subtype of interface(s). So you can use the object in any place where the interface(s) is(are) required. So it is type safely to invoke any methods of object "faces" because the object is subtype of all of that "faces".
    However the problem is that interface and object types are not assign compatible, because of different copy semantic and invoking mechanism.
    But in my samples only invoking mechanism is required. But some sort of "magic stuff" can make it seamless(as you can see it in MSIL of .NET where copy semantic and invoke mechanism for objects and interfaces are the same). Of course the ambiguity can arise and resolve time (supports for polymorphism), but it is not from type safety area.

    ReplyDelete
  10. I don't see a violation in type safety but in your implementation actually it violates the rules of Delphi type safety. It is the same reason why two exact same types are not assignment compatible - and that would be duck typing.
    And talking of lambdas and Delphi in the same sentence is kinda moot at the current point - Barry explained the problems the compiler currently has with them while ago).
    Anyway I hope once the 32-bit compiler gets migrated to the next gen architecture we can expect support for such language features.

    ReplyDelete