Thursday 29 May 2008

Ayende's weird code

I spent the better half of yesterday evening playing with some code that Ayende posted. He wanted to make this compile:

public interface IDuckPond
{
    Duck GetDuckById(int id);
    Duck GetDuckByNameAndQuack(string name, Quack q);
}

IDuckPond pond = null;
pond.Stub( x => x.GetDuckById );
pond.Stub( x => x.GetDuckByNameAndQuack );
My first reaction was WTF! I didn't understand at all what he was getting at, but obviously he was making sense to some people, as the meaningful (but initially over-my-head) comments were quickly piling up. Lets take a look. The interface definition is completely standard, nothing out of the ordinary at all. The weird bit is definately this Stub method. It appears to be a void method that accepts a lambda expression as its only parameter. But hang on a sec, lets forget the lambda expression for a second; this method isn't declared on the IDuckPond interface, and aren't we going to get a null reference exception anyway? Well, not if Stub is an extension method. Now its certainly unclear to me how Ayende was going to do anything meaningful within the extension method given that you can't use the ref or out keywords on the target of an extension method so you are still gonna be stuck with pond = null after the first stub call returns, but nevermind that. So the extension method is going to look something like this:
static class StubExtender
{
    public static void Stub(this IDuckPond pond, SOME_LAMBDA_THINGY)
    {}
}
Where SOME_LAMBDA_THINGY is obviously the lambda expression parameter bit. So what goes there? Well, lets take a look at the first lambda expression that gets passed to Stub:
x => x.GetDuckById
This certainly had me puzzled at first. He's not calling GetDuckById here, because there is no argument.. so what's going on? Thinking about it a bit I realised that the x.GetDuckById is a delegate, which means that the parameter to the Stub method is a function that returns a function. Brain starting to hurt yet? If so, you must be a functional programming newbie like me. Having made this realization, it was pretty quick to get the code compiling. I defined delegates for the two methods on IDuckPond:
delegate Duck GetDuckByIdDelegate(int id);
delegate Duck GetDuckByNameAndQuackDelegate(string name, Quack quack);
And then two overloads for the stub method:
public static void Stub(this IDuckPond pond, Func<IDuckPond, GetDuckByIdDelegate> func) { }
public static void Stub(this IDuckPond pond, Func<IDuckPond, GetDuckByNameAndQuackDelegate> func) { }
Okay, so the first overload takes a function that takes an IDuckPond and that returns a function with GetDuckById's signature. The second overload does the same thing but its parameter is a function that returns a function with GetDuckByNameAndQuack's signature. I quickly realised that I don't actually need the explicitly declare delegates, because I can just use the framework delegate Func, which I was already doing anyway. So the delegates got deleted and the overloads became this:
public static void Stub(this IDuckPond pond, Func<IDuckPond , Func<int, Duck>> func) { }
public static void Stub(this IDuckPond pond, Func<IDuckPond , Func<string, Quack, Duck>> func) { }
So this was better, but certainly not perfect. It was obvious that Ayende was looking for a general solution. The first generalization was simple.. make the stub method generic on the extension target:
public static void Stub<T>(this T obj, Func<T, Func<int, Duck>> func) { }
public static void Stub<T>(this T obj, Func<T, Func<string, Quack, Duck>> func) { }
The cool thing about this is that I didn't have to change the calling code at all. Specifically, I don't have to call pond.Stub<IDuckPond>(etc) - why? Because the compiler does generic parameter inference; it is able to determine that the type T is IDuckPond because we are passing in an IDuckPond as the first argument. But obviously this is still not ideal because as you add new methods to your interfaces, you will need to add the appropriate Stub overloads. You could probably code gen them, but code generating just for the sake of nice syntax seems to be taking things a bit far. I wanted to see if I could come up with anything nicer, and some of the comments in the thread gave me some ideas. This one is okay:
IDuckPond d = null;
d.Stub(x => (Func<int, Duck>)x.GetDuckById);
d.Stub(x => (Func<string, Quack, Duck>)x.GetDuckByNameAndQuack);
    
static class StubExtender
{
   public static void Stub<T, U>(this T obj, Func<T, U> func) { }
}
At least this way we can get by with just one definition for Stub, though at the cost of adding some casts. Now why did I have to add those awful casts? Well I modified the Stub method to be generic, but the compiler can't infer the generic parameter U like it is with T. Why? Lets say that IDuckPond had a third method, an overload of GetDuckById:
interface IDuckPond
{        
    Duck GetDuckById(int id);
    Duck GetDuckById(object o);
    Duck GetDuckByNameAndQuack(string name, Quack quack);
}    
Now when we say x.GetDuckById, its unclear which overload we are referring to, and its important because the delegates would have different signatures. As a result of this fact, the compiler cannot infer the type, so you have to give it explicitly, by casting. Some other neat alternatives were suggested by Jacob Carpenter, you can find those in the comment thread for Ayende's post if you are interested. He's doing something tricky with the default keyword, some investigation is in order methinks. Unfortunately there does not appear to be a perfect solution. You can't get away with the exact code that Ayende specified without writing or generating all the required overloads. I really enjoyed this little challenge, its reminded me that I'm not as good with C# as I would like to think, and instilled me with the urge to do more weird stuff. Expect more posts filled with angle brackets!

No comments:

Post a Comment