Sunday 15 March 2009

Explaining Expression

As part of planning for a user-group presentation, I've been trying to think how to distinguish between a delegate and an expression - ideally without mentioning the word "database".

For example, what is the difference between:

void Foo(Func<int,int,bool> func) {...}

and

void Foo(Expression<Func<int,int,bool>> func) {...}

? After all; both are called with identical syntax, for example:

Foo((x,y) => x==y);

My explanation

So here's my offering...
  • The delegate version (Func<int,int,bool>) is the beligerant manager; "I need you to give me a way to get from 2 integers to a bool; I don't care how - when I'm ready, I'll ask you - and you can tell me the answer".
  • The expression version (Expr<Func<int,int,bool>>) is the dutiful analyst; "I need you to explain to me - if I gave you 2 integers, how would you go about giving me a bool?"
In standard programming, the managerial approach is optimal; the caller already knows how to do the job (i.e. has IL for the purpose). But the analytic approach is more flexible; the analyst reserves the right to simply follow the instructions "as is" (i.e. call Compile().Invoke(...)) - but with understanding comes power. Power to inspect the method followed; report on it; substitute portions; replace it completely with something demonstrably equivalent, etc...

RPC Example

What does this mean? Well, for protobuf-net, I'm currently looking at the RPC stack. The design of protobuf-net is such that you *can* use code-generation, but it isn't enforced. I really want to avoid the messy approaches that you often need to use with WCF (for example).

Consider we have an RPC client that uses generics to indicate the interface that defines the service-contract (pretty common stuff if you are familiar with WCF):

class RpcClient<T> where T : class // T actually an interface
{}

Now; we can't expose the methods of T on RpcClient directly without using code generation; but we could use the approach of getting the caller to express their intent via the interface:

interface IFoo { string Bar(int i); }

var client = new RpcClient<IFoo>();
string s = client.Call(svc => svc.Bar(12345));

So what is Call? It wouldn't be useful as a delegate, as then we'd need to actually provide an IFoo instance to convince the caller to do the work for us... which means either code-generation or runtime type creation; the first is a pain, the second isn't even possible in some of the light-weight frameworks. But what about an expression?

TResult Call<TResult>(Expression<Func<T,TResult>> operation) {...}

Here, the caller gives us their version of what they would do with a hypothetical IFoo instance; we can take those instructions, dig out the MethodInfo (IFoo.Bar), evaluate the arguments (12345), and perform our own custom serialization on the wire. All without ever having an IFoo instance.

Application to Async

To take this a step further; consider Silverlight. All IO should be asynchronous. Yet we really don't want to have to start messing with Begin/End methods or events. We can use the same approach to let the caller express what they would do if running synchronously, but we'll execute it asynchronously. For example:

client.Call(svc => svc.Bar(12345),
result => Console.WriteLine(result()));

With

void Call(
Expression<Func<T,TResult>> operation,
Action<Func<TResult>> callback) {...}

The idea is that we will pick the Expression apart (like before), but rather than return the result, we'll execute the task asynchronously. The caller is requested to supply a callback which we'll invoke when we know the answer. Note that we don't give them the answer - we give them a function that they can invoke to find the answer; the distinction is that this also gives us a mechanism to convey an exception (i.e. if there was a problem the call to result() will re-raise the problem).

This also demonstrates mixing delegates and expressions in a single operation - we don't need to know what the caller wants with the answer in order to fetch it.

Summary

I've covered a lot in a small(ish) space, so feel free to pick it apart - but I hope this helps show some of the more interesting uses of Expression and delegates, and not a single database in sight!