In the new Delphi forums recently, Barry Kelly responded to a question about lambda expression syntax in Tiburón with this observation:

This syntax needs type inference. Our compiler was not originally written to support type inference, but work to support type inference is orthogonal to supporting anonymous methods. …  you’ll need to provide the full declaration type, for now.

In other words – as I understand it – Tiburón/Delphi 2009 will not (initially at least) support type inferencing.  To my mind this dramatically reduces the attractiveness of Generic Methods.

What Is Type Inferencing?

As the term suggests, (in this context at least) it is the ability of the compiler to infer the type of some symbol (variable or parameter etc) from the context or code around it.

In C# for example, it means that variables can be declared:

  var a = 10;

The compiler will infer from this that the type of variable a is int (you knew that, right?) without this having to be explicitly declared.

Pop Quiz: What do we think would/should the type of a be if Delphi were to support this?

Hint: What type would a be if it were a constant?

In general I find this to have dubious value, what, for example, can you deduce about the correct usage of a from this:

  var a = someRef.DisplayValue;

To know the type of a you know need to know the type of someRef in order to in turn know the return type of DisplayValue.  If browsing a project with all references intact and Code Insight, or equivalent, to deliver this information to you, then all good.  But if not, you are going to have a sticky time of it, and either way you still have to trust that the person that wrote the code – which perhaps wasn’t you – also knew those things and didn’t make their own inferencing error.

But what does type inferencing have to do with Generic Methods?

To understand that let’s look at why Generic Methods even come into it.

It all started this evening when I found myself needing to add another overload to my set of Exchange() methods, two of which I show here to give you the idea:

  procedure Exchange(var A, B: Integer); overload;
  procedure Exchange(var A, B: TObject); overload;

(The Exchange procedure exchanges the values of the two passed parameters)

My problem was that because the parameters to these procedures are, by necessity, var parameters, the compiler enforces strict type checking, so the second of the above declarations cannot be used with variables of a TObject derived type, but only variables explicitly and specifically of TObject type itself:

  var
    obj1, obj2: TObject;
    form1, form2: TForm;
  begin
    :
    Exchange(obj1, obj2); // OK - references will be exchanged
    Exchange(form1, form2); // ERROR: No compatible overload
  end;

The second call to Exchange() will not compile because the TForm type parameters are not compatible (from the compiler’s perspective) with the TObject overloaded version of the routine.  I have to add another overload with explicitly TForm type parameters.

Not for the first time recently I found myself thinking “If only I had Delphi 2009 – Generic Methods would make this so much easier!”.  Then I remembered reading Barry Kelly’s note about lack of type inferencing in Delphi 2009, and an alarm bell started ringing.

The Generic Solution

A generic implementation of an Exchange() procedure should be simple enough, and would go a little something like this:

  procedure Exchange<T>(var A, B: T);
  var
    i: T;
  begin
    i := A;
    A := B;
    B := i;
  end;

Which is nice and neat and cuts down on all those overloads. Unfortunately however, using this procedure is actually now more cumbersome, not less:

  var
    obj1, obj2: TObject;
    form1, form2: TForm;
  begin
    :
    Exchange<TObject>(obj1, obj2);
    Exchange<TForm>(form1, form2);
  end;

Ouch.

Frankly for the time it takes to create a new overload of a trivial routine like this, I would rather take those few seconds and reap the rewards later since – in common with most such routines – the code for the procedure will be written only once but code to call it will be written many, many times.

Voices Off: “But lots of overloads will pollute your namespace!”

You know what? That bothers me a lot less than creating unnecessary work for myself. I’m not the sort of developer that is unable to produce a line of code without invoking Code Completion. I may be unusual in this day and age, but I still write code faster than I can pick it from drop-down lists.

And this in Win32 Delphi at least is surely the biggest impact?  As far as the code goes,  any unused overloads will be pruned out by the linker.  In .NET – aiui – a cluttered namespace becomes a public nuisance, so the imperatives are somewhat different perhaps.

How Would Type Inferencing Help?

Well, assuming that any future Delphi type inferencing system could determine the appropriate type from the parameters (as it can in C#) then we could invoke our generic Exchange<T>() method by simply writing:

  var
    obj1, obj2: TObject;
    form1, form2: TForm;
  begin
    :
    Exchange(obj1, obj2);
    Exchange(form1, form2);
  end;

i.e. just as we can do with overloads.

But until this is possible at this stage I was feeling that I would continue with overloads until – at least – type inferencing were available.  They are just as type-safe and ironically produce “consumer” code that should be entirely compatible with a future generic methods implementation that is bolstered by type inferencing.

Using generic methods without type inferencing (in these sorts of cases at least) will simply create unnecessarily verbose and cumbersome “consumer” code.

But not being one to give in, I considered some alternative approaches.

A Truly generic Approach?

Alternatively, Delphi already provides a means to implement a truly generic (lower case “g’) Exchange() method – untyped parameters:

  procedure Exchange(var A, B);
  var
    i: ?
  begin
    i := A;
    A := B;
    B := i;
  end;

You will immediately notice of course that this implementation is neither valid nor complete. The type of i (for “intermediate”, if you were wondering) is not known and indeed not knowable.  And furthermore, the compiler simply won’t accept that A := B assignment since it doesn’t know the types of A and B it cannot know what instructions are needed.  For the same reason, the XOR trick won’t work either.  Those untyped parameters seem to have lead us to a dead end.

Not quite.  (If you are squeamish and/or don’t like smelly code you might want to avert your gaze about now, or at least pinch your nose):

  procedure Exchange(var A, B);
  var
    aa: Integer absolute A;
    bb: Integer absolute B;
    i: Integer;
  begin
    i  := aa;
    aa := bb;
    bb := i;
  end;

The absolute keyword is not something to be used lightly – it tells the compiler that the variables aa and bb exist at the same location as the parameters A and B – in this case the compiler even takes care of the fact that the parameters are passed by reference.

Since the variables are typed and the parameters are untyped, how can this be safe?

Well, frankly it isn’t.

In practice if the type of A and B is not the same size as an Integer (32-bits) then things are not going to go at all according to plan.

Equally of course though, if you only ever call this implementation routine with 32-bit sized parameters (which includes strings, object references etc) there won’t be a problem.  “IF”.

I should point out at this stage that I explored this approach as a curiosity.  I am certainly NOT recommending it!  For one thing it is not “truly generic” at all – it only appears to be but in fact has fairly strict conditions for correct use, and does not benefit from any assistance from the compiler to ensure that you do in fact use it correctly!

But we have one more trick up our sleeve – there is (at least) one more way to skin this particular cat that is safer than an untyped parameter approach and only a little more cumbersome to use than a non-type-inferenced Generic approach.

X-Rated Code – Being Explicit

A var parameter is syntactic sugar for passing by referencing and allowing modification of the de-referenced value.  We can of course achieve the same thing by taking care of the de-referencing aspects ourselves and explicitly passing references to values, rather than the values themselves:

  type
    PObject = ^TObject;

   :

  procedure Exchange(const A, B: PObject);
  var
    i: TObject
  begin
    i  := A^;
    A^ := B^;
    B^ := i;
  end;

And to call this:

  var
    obj1, obj2: TObject;
    form1, form2: TForm;
  begin
    :
    Exchange(@obj1, @obj2);
    Exchange(@form1, @form2);
  end;

All of which sits happily alongside any other overloaded versions of Exchange(), but which unfortunately requires consumer code that will not be compatible with a future Generic Method implementation of the routine.

BUT, even this won’t work if we are compiling with Typed @ Operator option enabled.

So I’m left a little stumped.

There are many ways to go about this.  If it weren’t for the fact that Generic Methods are nearly upon us I would favour the explicit de-referencing approach.  But the desire to create code today that will be compatible with impending new language features is quite compelling.  Then again, until we also get type inferencing, a Generic Methods based approach isn’t going to be compatible with anything we can write today anyway.

Despite myself, the untyped parameter approach could yet prove too tempting to resist (particularly if I find myself needing yet another class-specific version of Exchange()) since all the types that I’ve ever found myself wanting to Exchange() meet the 32-bit criteria.

It really hinges on when we might see type inferencing in a Delphi compiler.  If that is something we are likely only to see in Commodore then worrying about compatability of a few calls to Exchange() could prove somewhat misplaced given the far wider issues likely to arise from the move to a 64-bit compiler.

A Compromise?

In the meantime I wonder whether it would not be possible to have a compiler option to disable such strict type checking on var parameters, ideally on a method-by-method basis.

After all, we still have such an option for short string var parameters (although having never had cause to use it I don’t know if it works in quite the way I have in mind – i.e. on declarations of rather than calls to, methods).

In the case of class-type var parameters the more relaxed type checking could allow parameters of any type correctly derived from the formal type:

  interface
    {$VARSTRICTCLASSTYPE OFF}
    procedure Exchange(var A, B: TObject);
    {$VARSTRICTCLASSTYPE ON}

  implementation

    procedure Exchange(var A, B: TObject);
      :
    end;

Then again, I can’t think of any other concrete examples where such a capability would have any practical use.  But if this were possible to implement more quickly and safely than a comprehensive type inferencing system, I think I’d take it to keep me going.

🙂

7 thoughts on “Generic Methods and Type Inferencing

  1. Pingback: strict
  2. In Tiburon, global procedures cannot be generic; however, you can create generic class methods.

    You’ve gotten the wrong idea about the Exchange example, however. The problem of type inference in lambdas is different from the problem of inferring type arguments for a generic method call.

    For calls, the arguments are parsed as fully typed expressions. Typing occurs from the bottom up. The elements of the expression all have a type, and operators and overloads are resolved based on these types; the types ripple all the way up, until the entire argument list is well-typed.

    Thus, inferring type arguments for a generic method call is pretty simple (ignoring any competing overload issues etc.): you look at the types of all the arguments and try to unify them with the type parameters.

    However, the problem of type inference for lambdas is an entirely different order of problem, because the body of the lambda is *not* well-typed. For a lambda in C# that look like:

    a => a.Foo

    or:

    (a, b) => a.Bar(b)

    … the compiler, while parsing the lambda, has *no* idea what type a is, or what type b, so it doesn’t know what Foo is being talked about, and it can’t perform overload resolution on Bar(b) such that it can discover the correct return type, etc.

    Instead, the parser needs to parse the entire lambda just as a syntax tree, without any typing information. It’s only later, when the lambda gets assigned to a location of type Func or Expression<Func> or similar that type inference comes into play. The compiler then has a much tougher job: it needs to look at what has already been resolved, stick it into the lambda and try to type it, see what else falls out from that (perhaps a return type is now well-typed because one of the parameter types was specified in the receiver of the lambda), etc.

    It’s this parsing logic that the Delphi compiler isn’t designed to support. It was written with the assumption that it knew the types of every element upon parsing them.

    That’s what I meant in the post.

    PS: Your example, in Tiburon, almost works. Method type inference works well for arguments passed by value. It doesn’t work for arguments passed by reference, unfortunately (the compiler is being too strict).

  3. Thanks for the comments Barry, although I wasn’t considering Lambda’s at all, only Generic Methods and considering the implications that a lack of type inferencing might have.

    You seem to be saying that there will actually be limited type inferencing (in the case of generic class methods), in Tiburon. Or at the very least that it would be easily possible.

    But given that you seem to be saying it wouldn’t quite work-out in the case of var parameters anyway, I’m not sure that would help in the the specific case I was considering.

    Is there any merit in that new compiler option I speculated about? 😉

    I can’t think of another example where such an option would have practical uses though so instead, how about a built in “compiler” implementation of a generic Exchange() mechanism? (Should probably be called Swap() though, since that at least is already defined in System which has a similar use albeit at the byte-level on a single param).

    It’s also a little disappointing to hear that genericity is moot as far as unit procedures are concerned.

  4. Pingback: Anonymous

Comments are closed.