11

Question:

Is there a way to do duck typing with Delphi 2007 (i.e. without generics and advanced Rtti features)?


Duck typing Resources for Delphi 2010 onward:

Last Edit:

I've delved more into the resouces listed above and studied every posted answers here.

I end up refining my requirement a made a follow up post to this question.

Community
  • 1
  • 1
menjaraz
  • 7,551
  • 4
  • 41
  • 81

3 Answers3

10

With the help of the ObjAuto.pas and invokable variant types it should be possible (written in XE but should also run in Delphi 7 or lower):

unit DuckTyping;

interface

function Duck(Instance: TObject): Variant;

implementation

uses
  ObjAuto,
  SysUtils,
  TypInfo,
  Variants;

type
  TDuckVarData = packed record
    VType: TVarType;
    Reserved1, Reserved2, Reserved3: Word;
    VDuck: TObject;
    Reserved4: LongWord;
  end;

  TDuckVariantType = class(TPublishableVariantType)
  protected
    function GetInstance(const V: TVarData): TObject; override;
  public
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    function DoFunction(var Dest: TVarData; const V: TVarData;
      const Name: string; const Arguments: TVarDataArray): Boolean; override;
  end;

var
  DuckVariantType: TDuckVariantType;

{ TDuckVariantType }

procedure TDuckVariantType.Clear(var V: TVarData);
begin
  V.VType := varEmpty;
  TDuckVarData(V).VDuck := nil;
end;

procedure TDuckVariantType.Copy(var Dest: TVarData; const Source: TVarData;
  const Indirect: Boolean);
begin
  if Indirect and VarDataIsByRef(Source) then
    VarDataCopyNoInd(Dest, Source)
  else
  begin
    with TDuckVarData(Dest) do
    begin
      VType := VarType;
      VDuck := TDuckVarData(Source).VDuck;
    end;
  end;
end;

function TDuckVariantType.DoFunction(var Dest: TVarData; const V: TVarData;
  const Name: string; const Arguments: TVarDataArray): Boolean;
var
  instance: TObject;
  methodInfo: PMethodInfoHeader;
  paramIndexes: array of Integer;
  params: array of Variant;
  i: Integer;
  ReturnValue: Variant;
begin
  instance := GetInstance(V);
  methodInfo := GetMethodInfo(instance, ShortString(Name));
  Result := Assigned(methodInfo);
  if Result then
  begin
    SetLength(paramIndexes, Length(Arguments));
    SetLength(params, Length(Arguments));
    for i := Low(Arguments) to High(Arguments) do
    begin
      paramIndexes[i] := i + 1;
      params[i] := Variant(Arguments[i]);
    end;

    ReturnValue := ObjectInvoke(instance, methodInfo, paramIndexes, params);
    if not VarIsEmpty(ReturnValue) then
      VarCopy(Variant(Dest), ReturnValue);
  end
  else
  begin
    VarClear(Variant(Dest));
  end;
end;

function TDuckVariantType.GetInstance(const V: TVarData): TObject;
begin
  Result := TDuckVarData(V).VDuck;
end;

function Duck(Instance: TObject): Variant;
begin
  TDuckVarData(Result).VType := DuckVariantType.VarType;
  TDuckVarData(Result).VDuck := Instance;
end;

initialization
  DuckVariantType := TDuckVariantType.Create;

finalization
  FreeAndNil(DuckVariantType);

end.

You can simply use it like this:

type
  {$METHODINFO ON}
  TDuck = class
  public // works in XE, not sure if it needs to be published in older versions
    procedure Quack;
  end;

procedure TDuck.Quack;
begin
  ShowMessage('Quack');
end;

procedure DoSomething(D: Variant);
begin
  D.Quack;
end;

var
  d: TDuck;
begin
  d := TDuck.Create;
  try
    DoSomething(Duck(d));
  finally
    d.Free;
  end;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
6

Quick answer:

Not in a meaningful way

Longer answer: According to the wiki page "Duck Typing" is identified by:

In duck typing, one is concerned with just those aspects of an object that are used, rather than with the type of the object itself. For example, in a non-duck-typed language, one can create a function that takes an object of type Duck and calls that object's walk and quack methods. In a duck-typed language, the equivalent function would take an object of any type and call that object's walk and quack methods. If the object does not have the methods that are called then the function signals a run-time error.

The equivalent Delphi non-compilable code would look like this:

procedure DoSomething(D);
begin
  D.Quack;
end;

I intentionally did not specify a type for D because that would defeat the purpose. Delphi is statically typed so this would never work. If you need this for some small functionality you can use Interfaces or RTTI and get something like this:

procedure DoSomething(D:TObject);
begin
  (D as ISomeIntf).Quack;
end;

If you can get RTTI:

procedure DoSomething(D:TObject);
begin
  CallQuackUsingRTTI(D);
end;

I have personally used the RTTI method to identify (and manipulate) list objects in a way that makes the code work with both TList descendants and generic TList<T> variants.

The take-away from this should be: Even with advanced functionality in the newest versions of Delphi (generics and comprehensive RTTI) you'll only get close to Duck typing for limited functionality and with significant effort. This is simply not in the DNA of Delphi (because Delphi's DNA says "static typing"), but you might be able to get something close enough, and with a lot of effort, and only for specific functionality. Maybe if you give us an idea of what specific functionality you'd like, we'd be able to figure something out.

Community
  • 1
  • 1
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • Thank you for answering. I have delved into the source codes of the 3 resources I have cited and come to this conclusion: They achieve Duck typing using Interface, generics and full fledged Rtti (Duck Duck Delphi targets Delphi XE2, the rest can do with Delphi XE). – menjaraz Feb 29 '12 at 13:20
  • I want to be able to manipulate any arbitrary list object implementing 1) `function Count: Integer;` 2) `function GetItem(const index: Integer): TObject;` 3) `procedure Add(const AObject: TObject);` 4) `procedure Clear;` in a Duck typing fashion in Delphi 2007. – menjaraz Feb 29 '12 at 13:40
  • @menjaraz: Then create an interface that has those 4 methods, and use `Supports()` to check whether a list object implements this interface. – mghie Feb 29 '12 at 14:00
  • @mghie: This precludes manipulation (in Duck typing fashion like) of other objects having implemented the methods I enumerated but without Interface. Am I right? – menjaraz Feb 29 '12 at 14:13
  • @menjaraz: True, but would that be a problem? – mghie Feb 29 '12 at 14:32
  • @menjaraz: To state it another way: Why would you prefer duck typing (unsafe) over casting to an interface (or checking whether the object implements the interface before casting to it), thus keeping the type safety afforded by using Delphi? This for me is the perfect way to concern oneself "with just those aspects of an object that are used, rather than with the type of the object itself". Do you never have overloaded methods? – mghie Feb 29 '12 at 23:48
  • @mghie: Please see Daniele Teti's [blog post](http://www.danieleteti.it/2011/11/08/duck-typing-in-delphi/). I don't want to introduce depency on a `Superlayer type` for any list type I have to use in my business objects. This can be achieved with DORM. – menjaraz Mar 01 '12 at 04:08
4

Here's an idea that requires you create a type library.

Use OLE Automation types, and implement Dispatch Interfaces (dual COM objects).

Now you Now you can write whatever you want after that type, and we'll be finding out at runtime whether or not it works, or blows up. Welcome to dynamic typing.

procedure DoSomething(D:OleVariant);
begin
  D.Quack; // Might work, might blow up.
end;

I consider it ugly, but others might not.

Warren P
  • 65,725
  • 40
  • 181
  • 316
  • +1. That seems like a really, really long way around. I tried implementing that as a test in a console application and failed (gave up when I realized I need a registered type library) - but my COM and OLE knowledge is pretty close to zero. And on a different note, your function has no return type :) – Cosmin Prund Feb 29 '12 at 12:53
  • Delphi makes it really easy to write dual types, and inside the application, you need a type library, but you can register it locally, without having to register it outside Delphi, using SideBySide (sxs) COM. By Ugly, yes, I mean "long way around", but it does work. However, if anybody wanted to do this in an application I had anything to do with, I'd reject it out of hand. But as it meets the OP's needs, at least strictly speaking, it's an option. – Warren P Feb 29 '12 at 13:48
  • 2
    +1, but I would remove the first sentence. There is nothing ugly about this (once one has decided to *use* duck typing, which is an alien concept in Delphi). I'd rather see this being the accepted answer... – mghie Feb 29 '12 at 13:56