-
-
Notifications
You must be signed in to change notification settings - Fork 424
Thinking Functionally: Currying
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:
- By passing a tuple of arguments as a single parameter
- 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:
- Construct the field called "printTwoParameters" which is a
Func
but with only one parameter:int x
and that returns aFunc<int, Unit>
. - Inside that, construct a sub-function that has only one parameter:
int y
. Note that this inner function uses thex
parameter butx
is not passed to it explicitly as a parameter. Thex
parameter is in scope, so the inner function can see it and use it without needing it to be passed in. - Finally, return the newly created subfunction.
- This returned function is then later used against
y
. Thex
parameter is baked into it, so the returned function only needs they
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 hasx
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 usingFunc
however.