Thinking Functionally: Currying

Paul Louth edited this page May 17, 2018 · 7 revisions

After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an C# function can have more than one?

This is done one of two ways:

  1. By passing a tuple of arguments as a single parameter
  2. A function with multiple parameters is rewritten as a series of new functions, each with only one parameter. This is called "currying", after Haskell Curry, a mathematician who was an important influence on the development of functional programming.

To see how this works in practice, let’s use a very basic curried example that prints two numbers:

    static Func<int, Func<int, Unit>> printTwoParameters = x => y =>
    {
        Console.WriteLine($"x={x} y={y}");
        return unit;
    };

Let’s examine this in more detail:

  1. Construct the field called "printTwoParameters" which is a Func but with only one parameter: int x and that returns a Func<int, Unit>.
  2. Inside that, construct a sub-function that has only one parameter: int y. Note that this inner function uses the x parameter but x is not passed to it explicitly as a parameter. The x parameter is in scope, so the inner function can see it and use it without needing it to be passed in.
  3. Finally, return the newly created subfunction.
  4. This returned function is then later used against y. The x parameter is baked into it, so the returned function only needs the y param to finish off the function logic.

By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use printTwoParameters, you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two:

    // eval with one argument
    Func<int, Unit> f = printTwoParameters(1);

If you evaluate it with one argument, you don't get an error, you get back a function.

So what you are really doing when you call printTwoParameters with two arguments is:

  • You call printTwoParameters with the first argument (x)
  • printTwoParameters returns a new function that has x baked into it.
  • You then call the new function with the second argument (y)

Here is an example of the step by step version, and then the normal version again.

    // step by step version
    var x = 6;
    var y = 99;
    var intermediateFn = printTwoParameters(x); // return fn with 
                                                // x "baked in"

    var result  = intermediateFn(y); 

    // inline version of above
    var result  = printTwoParameters(x)(y);

As you can see from the inline version, the nasty side-effect of currying is that we end up writing our arguments list like so: (x)(y), which is ugly in anybody's book. And declaring it as:

   Func<int, Func<int, Unit>> f = x => y =>

is also burdensome as well as imparts a run-time cost.

So we can declare printTwoParameters the 'classic way':

    static Unit PrintTwoParameters(int x, int y)
    {
        Console.WriteLine($"x={x} y={y}");
        return unit;
    }

And then call curry from Prelude:

    var printTwoParameters = curry(PrintTwoParameters);

    var x = 6;
    var y = 99;
    var intermediateFn = printTwoParameters(x); // return fn with 
                                                // x "baked in"

    var result  = intermediateFn(y); 

    // inline version of above
    var result  = printTwoParameters(x)(y);

NOTE: When using a method as an argument to curry you will need to provide the generic arguments that represent the arguments and return type of the method being curried. I have omitted them from the examples above for clarity. No such limitation exists when using Func however.

NEXT: Partial application

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.