My recent post, C# Program Entirely With Static Methods, got lots of great comments. Indeed, as is often the case, the comments are in many ways a better read than the original post. However, there were several commenters who claimed that C# does not have partial application. I take issue with this. Any language that supports higher order functions, that is, functions that can take functions as arguments and can return functions, by definition, supports partial application. C# supports higher order functions, so it also supports partial application.
Let me explain.
Let’s start by looking at partial application in F#. Here’s a simple function that adds two numbers (you can type this into F# interactive):
>let add a b = a + b;;
Now we call use our ‘add’ function to add two numbers, just as we’d expect:
> add 3 4;; val it : int = 7
But because F# supports partial application we can also do this:
> let add3 = add 3;; > add3 4;; val it : int = 7
We call add with a single argument and it returns a function that takes a single argument which we can then use to add three to any number.
That’s partial application. Of course, if I try this in C# it doesn’t work:
Red squiggly line saying “delegate Func has two parameters but is invoked with one argument.
Case proven you say: C# does not support partial application!
But wait!
Let’s look again at the F# add function. This time I’ll include the response from F# interactive:
> let add a b = a + b;; val add : a:int -> b:int -> int
This shows us the type of the add function. The important bit is: “a:int –> b:int –> int”. This tells us that ‘add’ is a function that takes an int and returns a function that takes an int and returns an int. It is not a function with two arguments. F# is a restrictive language, it only has functions with single arguments. That is a good thing. See Mark Seemann’s post Less is More: Langauge Features for an in depth discussion of how taking features away from a language can make it better. When people say “F# supports partial application” what they really mean is that “F# functions can only have one argument.” The F# compiler understands the syntax ‘let add a b = …’ to mean “I want a function that takes a single argument and returns a function that takes another single argument.”
There’s nothing to stop us from defining our C# function with the same signature as our F# example. Then we can partially apply it in the same way:
There you are: partial application in C#. No problem at all.
“But!” You cry, “That’s weird and unusual C#. I don’t want to define all my functions in such a strange way.” In that case, let me introduce you to my friend Curry. It’s not a spicy dish of South Asian origin but the process of turning a function with multiple arguments into a series of higher order functions. We can define a series of overloaded Curry extension methods:
We can then use them to turn ‘ordinary’ C# functions with multiple arguments into higher-order functions which we can partially apply:
Thinking more about Mark Seemann’s blog post, it would be an interesting exercise to start to take features away from C# whilst keeping syntactic changes to a minimum. If we took away multiple function arguments, classes, interfaces, nullable types, default mutability etc, would we end up with a subset language that would be perfect for functional programming, but still familiar to C# developers? You would of course lose backward compatibility with existing C# code, so the incentive to do it isn’t that great, but it’s a fascinating thought experiment.
5 comments:
... and the book will be titled 'C#: The Good Parts'. Or maybe it will be 'C#: The Functional Parts'.
Or you could write add3 as :
Func add3 = (b) => add(3,b);
which I see as far less of a stylistic crime than
var add3 = add.Curry()(3);
Nice article, I featured it in the latest issue of C# digest.
To quote the late, great Spike Milligan: "Put it in the Curry!!!" ;-)
Post a Comment