Expression Trees: abusing operator overloading for fun and profit

Quick, what should this routine produce?

procedure Test;
var
   expr: TExpression;
   result: string;
begin
   expr := 'Value';
   expr := expr = 5;
   result := expr;
   writeln(result);
end;

There are three basic answers here:

  1. Wait, you’re assigning a string to it, and then a boolean comparison against an integer… does that even compile?
  2. Well, first you’re assigning a string, then a boolean comparison against an integer, then turning it into a string… well, TExpression must be some sort of thing like a Variant.  So the output should be “False”.
  3. If TExpression is a record, the output could be just about anything.

If you answered 3), you’re probably Stefan Glienke, or someone like him who already knows the trick.  When I do this, the output is “(Value = 5)”.  If you want to know how that’s possible in ordinary Delphi code, read on.

Operator overloads were introduced several releases ago, and they’re very interesting things.  They allow you to add custom behavior to records by mapping operators to functions.  These functions are just ordinary functions, with a few restrictions: they must have a certain number of arguments, the type of at least one of the arguments has to be the record type in question, and for a few specific operators, the other argument has to be a boolean.  That’s it.

One restriction that notably does not exist is any definition of what the return type should be… even on comparison operators that you would expect to return a boolean, such as the = operator!

Let me repeat that, because it’s critical to understanding this point, and it kind of blew my mind the first time I figured it out:  an operator overload, even a comparison operator, is a function that can return any data type, even if it’s an operator you would expect to always return a boolean.

Therefore, if my TExpression has an = operator defined that takes a TExpression and an integer and returns a new TExpression, the code example above suddenly makes a whole lot more sense.

Well sure, that’s a cute trick, but why is this interesting?  Because you can use this principle to build stuff like expression trees, which are a major part of what makes LINQ so cool.  Expression trees are a feature where, instead of turning your LINQ expression into executable code, the compiler turns it into a data object that represents the expression to be evaluated.  This expression tree can then be passed to a LINQ provider object, which acts on the tree.  So a database provider would turn your expression into part of a SQL query, whereas LINQ-to-Objects might turn the same expression into a filtering method.

I mentioned Stefan Glienke because he’s implemented quite a bit of functionality based on these basic principles in his DSharp library.  Unfortunately, he seems to have briefly acknowledged the existence of the concept of query providers a few years back, and then forgotten about it entirely.  There’s a simple SQL provider which generates a WHERE filter string from an expression tree… and then does nothing with it.  It looks a lot like a bit of “check in initial code and then come back to it later” that never got came back to later.  (That might be grammatically incorrect, but you know what I mean.)

Apparently the folks at DevArt have done something very similar in their EntityDAC product, though it’s difficult to say much about it because there’s no source available to poke around in.

Has anyone seen any other examples of Delphi expression trees, or any good ideas on what this sort of operator overloading hack could be used for?  Let me know in the comments.

2 Comments

  1. I never continued work on it because you cannot create anything like LINQ in Delphi.
    The language completely lacks any possibility to get an expression tree of a delegate.
    This is different in .Net because there passed lambdas can be converted to an expression tree aswell.

    In Delphi everything that people call LINQ is NOT LINQ, its some SQL statement builder ersatz.

    You simply cannot write for c in customers.Where(c => c.City = ‘Berlin’) do in Delphi.
    While you can use an equivalent for this statement in Delphi using some techniques it will never be anything like LINQ.

    Also see my comment here: https://plus.google.com/104189918092978951213/posts/Q7hL2hrdCUq

  2. Francisco Ruiz says:

    Hello there,

    Operator overloading was the last language gem done at Delphi years ago.
    When I first use them, I saw the potential behind. They enrich the language in a beautiful clear and expressive way.
    But after some work I stop using them because lack of three key element:

    - ARC or GARBAGE collection (Introduced later).
    - Open Operator overloading, not only the 30 standard ones.
    - Lambdas.

    This is my experience with operator overloading.

    Regards,
    Francis

Leave a Reply