Functional Programming in C# - Currying
Everytime I make a post about C# or any other statically typed language, I get hit by a ton of comments from dynamic and functional language programmers who look down upon me for using a statically typed language(*cough*, *cough* ;-) ). And being a big fan of Python and 'the Lisp way' myself, I have to admit that there are times when I miss the higher-order function support I get in languages like Haskell.
Until now that is :-)
With C# 2.0, you get something called anonymous delegates. On the surface, this seems to be syntactic sugar around writing a delegate. But in reality, there is something more magical going on underneath the covers - something called 'closures'. However, C# anonymous methods are not closures in the true sense of the word.
And thanks to these closures, we can do things now in C# 2.0 that we could only dream about in other statically typed languages .(see Don's post for some cool things that become possible). For example, currying. If curry conjures up images of spicy Indian food, head on over to this Wikipedia article to see what I'm talking about.
Instead of explaining currying again, let me point to a blog post I made in an earlier life where I talked about implementing this in Python.
Basically, currying refers to function builders. Instead of writing a series of functions which differ slightly, you can write one uber-cool function and have it generate functions for you at runtime.
Now, let's see how we can do the same currying in C# 2.0. I really don't care how the code for the curry() function looks like but I want the actual function invocation to have the same syntax as it does in Python.
Curry when implemented in C# 2.0 looks something like this
public class Lambda
public delegate T Function<T,T1>(params T1 args);
public static Function<K,K1> Curry<K,K1>(Function<K,K1> func, params K1 curriedArgs)
return delegate(K1 funcArgs)
//Create a final array of parameters, having the curried parameters at the beginning, followed
// by the arguments at the time the generated delegate is called
K1 actualArgs = new K1[funcArgs.Length + curriedArgs.Length];
funcArgs.CopyTo(actualArgs, curriedArgs.Length );
//Call the function with our final array of parameters.
return func( actualArgs );
Things to note here
- 'K' is the return type of the function that is going to be curried. K1 is the type for the param array
I know that the code doesn't look as friendly as the Python code but hey, I'll take what I can get.:-) Also, since this code will probably lie in some library somewhere, the complexity is not really a factor.
The only problem I ran into was that anonymous delegates can't use param arrays but that didn't matter in the end. The syntax for variable length arguments isn't as friendly as it is in Python but then, I doubt that Anders anticipated them to be used this way in C# :-)
Now that we have written ourselves a handy-dandy curry function, let's take it for a spin. Similar to the Python multiply example above, I want a adder-builder function.
Let's write the actual function first. This takes any number of integers and returns the sum of all of them.
public static int Add (params int numbers)
int result =0;
foreach (int i in numbers)
result += i;
I want to modify this Add function so that it not only adds all its parameters but it also adds 7 to the result and returns the answer (talk about contrived examples :-) ). Instead of writing a wrapper function, I can just go ahead and use our shiny new Curry method.
adder(5, 6); // returns 18
What's happening here? Our 'Curry' function generates a new function for us which appends 7 to the parameter list. When we invoke the 'adder' delegate with 5 and 6 as the arguments, in reality, we're calling Add with 5,6 and 7 as the parameters. The magic part is that you can create another curry passing this 'adder' delegate. Want to add '2' to whatever 'adder' returns? You can go ahead and do this
Lambda.Function<int,int> adder2 = Lambda.Curry<int,int>(adder, 2);
adder2(5, 6); //returns 20 ( 5+ 6 + 7 + 2)
Mind-bending stuff. :-)
Update: Changed the code to a generic method instead of a generic class as suggested by Don Box
Update: Linked to Brad's post on why anonymous methods are not closures