Wednesday 29 February 2012

Add some Curry to your delegates

I started my love relationship with Groovy 1 year ago, but unfortunately since then I haven't had almost any time to devote to it. In the last days I'm trying to go back to it, and one of the first things to do was reading the list of additions to Groovy 1.8, and among all that sweet stuff, the Closure related things immediatelly caught my eye.

I very much like having a curry method in the Closure class. Well, first of all, let's clarify a bit. The "Curry" term is wrongly used here (it's not a crime, even the clever guys at Prototype.js seem to use the term incorrectly), what they are calling currying is in fact Partial Function Application. Well, we have to bear in mind that the term Closure itself is used in a bit inappropiate way in Groovy, I would say when these function objects are not trapping the free vars (cause they are not using them), we should not call them closures, they're just Function Objects (implemented same as in C# via compiler wisdom that creates new classes inheriting from Closure and with a new doCall method).
OK, let's forget this nomenclature thing, and back to the business... What mainly called my attention is that I remember having implemented my own Partial helper methods in C# sometime ago, but due to the (mainly) static character of C# and its delegates, we need a different Partial implementation for each type of delegate that we're using. Let's see an example:

public static Func<T2, R> Partial1<T1, T2, R> (this Func<T1, T2, R> originalFunc, T1 arg1)
 {
  return (arg2) => originalFunc(arg1, arg2);
 }

if we wanted this to work for another delegate, let's say Func we would need a new Partial method, and so on, and so on. Then, the question seems obvious, can't we write a generic Partial method that will work for all sorts of delegates that we happen to throw at it?

Well, each time we define a new delegate type the compiler creates a new class that inherits from the abstract class MulticastDelegate and has an Invoke method with the correct signature for this delegate. MulticastDelegate has a concrete DynamicInvoke method that works for any sort of parameters (receives a params array...). Obviously this kind of invocation involves a lot of runtime checks (as contrary to what happens with a normal delegate invocation done through the autogenerated Invoke method, because nothing is known at compile time with regards to the correctness of these parameters...).

So, with this DynamicInvoke method in mind, let's leave performance aside as this is mainly a proof of concept thing, and let's see what we can come up with:

public delegate Object GenericDelegate(params object[] args);

public static GenericDelegate Partial (this Delegate originalDelegate, object arg1)
 {
  GenericDelegate deleg = delegate (object[] args)
  {
   List<Object> lst = args.ToList();
   lst.Insert(0, arg1);
   return originalDelegate.DynamicInvoke(lst.ToArray());
  };
  return deleg;
 }
It's rather interesting to note two gotchas that I came across while doing this:
  • You can't use the params keyword when you declare an anonymous method. Well, it makes sense, the place where it really has to go is when you declare the delegate type that you'll use to refer to that anonymous method (GenerateDelegate in our case).
  • The other important, and at first sight shocking, point, is that my initial code:
      Delegate deleg = delegate (object[] args)
      {
       List<Object> lst = args.ToList();
       lst.Insert(0, arg1);
       return originalDelegate.DynamicInvoke(lst.ToArray());
      };
    
    was raising this compilation error:
    error CS1660: Cannot convert anonymous method to type "System.Delegate" because it is not a delegate type
    Seems pretty odd, how is it that a System.Delegate is not a delegate type?
    Well, let's forget about the wording and let's see it this way: the compiler needs to invoke the constructor of a concrete Delegate type to "sort of wrap" the Anonymous Method. System.Delegate and System.MulticastDelegate are abstract types, so they are of no use here, we need a Concrete Delegate type, that's why I needed to declare a new delegate type for this, in my case:
    public delegate Object GenericDelegate(params object[] args);

You can download the code here

No comments:

Post a Comment