Currying functions! There are probably a million articles out there on what it is and how to do it. This’ll make that count go up by one. But I’d also like to present an answer as to why you might want to do this.
So What Is It?
It’s composing single-argument functions in such a way as to be able to pass multiple arguments to them. That is the kind of sentence that demands an example, though. We’re going to work this up in C# for two reasons. One, it’s a common language that has first-class functions. Two, it doesn’t get used in this way very often.
The Scenario
It’s a contrived example, but it’s one from my personal history. Let’s say we want to calculate the total volume, in pounds, of weight lifted for a competition. The individual weights are denoted in kilograms, so we’re going to have to do some math to get these numbers into Hamburger Units. If you’re keeping track, this means that we’re going to multiply a weight by 2.20462 (to go from kilograms to pounds) and then again by the number of reps performed.
This is entirely straightforward to do in C# and absolutely does not require us to curry any functions. We’re doing it anyway!
The Code
This is where things get a little strange if you’re used to C# or other, similar languages.
The normal, sane approach would be to dive right in and make a function that takes two arguments, reps and weight, and then does some arithmetic and returns a result.
The weirder approach is to begin with the idea that we’re going to only do this with single-argument functions. First thing we need? A multiplication function. Fair warning: C# gets a little verbose because we need to specify the return type of the function. Without further ado, let’s write our multiplication function.
public Func<double, double> Multiply(double x){
return (double y) => {
return x * y;
};
}
This is the basis for what we’re going to be doing. Let’s talk about it!
First thing to notice is that this has a bit of a weird return type. “Func<double, double>” is signifying that this thing is going to return a function that takes a double as a parameter and returns a double out the other side. The next thing to notice is that we’re returning an anonymous function that also only takes a single argument. Within that anonymous function, the variable “x” is still in scope and so we can use it as an operand, along with “y,” to do some very basic multiplication.
How would you invoke this? Easy! If you want to multiply, say, three and two, it’d look like this:
Multiply(3)(2); //this’ll be 6
Since it returns a function that takes a single argument, invoke that anonymous function right away with a new value.
Currently, this isn’t especially useful. We’ve just invented a dumber way to do multiplication versus what C# gives you out of the box, so let’s expand on this a little bit. We’re going to use our new multiplication approach in a function called “ConvertToLbs” so we can encapsulate the step where we take a value in kilograms and get a value in pounds back out.
public double ConvertToLbs(double kgs){
var conversionValue = 2.20462;
return Multiply(conversionValue)(kgs);
}
Notice how the first argument is hard coded right in there. I gave it a variable name so you’d know what it was doing (so that it wasn’t just a magic number), and it’s stuck in the invocation of “Multiply.” This means that when we call this function, we only provide one argument because the other one is baked in. Like so:
ConvertToLbs(24); //this’ll be 52.9109
We’re almost done now. We’ve got the necessary building blocks in place to write the last function that we need to get a total! Let’s have a look at that one, now:
public Func<int, double> DeriveTotal(double weight){
return (int reps) => Multiply(ConvertToLbs(weight))(reps);
}
This one will return a function that takes an integer and returns a double. You might notice that it looks a lot like “Multiply” in that it takes two arguments and multiplies them. There are a couple of distinctions, though.
The first distinction is that we’re specifying that we want to multiply by an integer. This is because in weightlifting, you don’t get partial reps; you either complete one or you don’t. So we have a little type information baked in. The other distinction is that there is an interim step where a value is converted to different units of measurement, and we’re able to wrap that up in the behavior of this function.
So let’s say that someone used a 24kg weight and did 100 reps of something. That’d look like this:
DeriveTotal(24)(100); //this’ll be 5,291.088
So altogether, our tiny little program is:
public Func<double, double> Multiply(double x){
return (double y) => {
return x * y;
};
}
public double ConvertToLbs(double kgs){
var conversionValue = 2.20462;
return Multiply(conversionValue)(kgs);
}
public Func<int, double> DeriveTotal(double weight){
return (int reps) => Multiply(ConvertToLbs(weight))(reps);
}
If you’re familiar with C# to any degree, it should be trivial to concoct the non-curried version of this operation. If it’s so trivial, why would you even want to do this?
Motivated Reasoning (The Answer to “Why?”)
I think it’s neat, so I wanted to do it this way. The reason why I think this is neat is that I was working my way through getting familiar with Clojure some years ago, and I had a moment where I was absolutely stumped. I had a set of things I wanted to do with my data, and I could not figure out how to get Clojure to do them sequentially the way that I wanted. I expended an embarrassing amount of thought on this until I realized what I actually wanted to do was brew up a bunch of functions that each did a small part of the whole. Clojure got a lot easier after that.
But this makes you think about what you’re doing in a different way. Our job, as developers, is to be able to think. Even if this is just an exercise in doing stuff that you’d never do in production code, it’s still useful to be able to reach into the metaphorical toolbox and pull out a way to think differently. Maybe, as a result of this kind of thinking, you can break up a function into some smaller units and subject those to individual tests!
Lastly, and perhaps most importantly, imagine the sheer joy of befuddling all of your coworkers and collaborators by constructing all of your solutions in this manner. I really don’t recommend you do this, but it sure is fun to think about, briefly.