Thursday, September 10, 2015

Partial Application in C#

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:

image

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:

image

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:

image

We can then use them to turn ‘ordinary’ C# functions with multiple arguments into higher-order functions which we can partially apply:

image

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:

Eric Potter said...

... and the book will be titled 'C#: The Good Parts'. Or maybe it will be 'C#: The Functional Parts'.

James Curran said...

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);

Garfík said...
This comment has been removed by the author.
Garfík said...

Nice article, I featured it in the latest issue of C# digest.

Roger said...

To quote the late, great Spike Milligan: "Put it in the Curry!!!" ;-)