Currying can be confusing. Let’s break it down
You’re a programmer who’s heard the term “curry” thrown around. Perhaps you’ve used partial application, and assumed they are the same thing. They’re not.
After reading this guide of understanding and enlightenment, you too will join the elite crowd of programmers that know the difference.
Accurate Currying Resource
It was with this horrible realization that I felt it was my duty to spread knowledge and awareness of this plight on programmers, and to provide a sane, accurate, and useful article.
With that said, before we jump into how to use currying, it’s crucial that we understand a few terms:
A language is said to support first-class functions if functions can be stored in variables and treated like regular objects, such as being passed into and returned by other functions.
A function that operates on other functions, either by accepting a function as input, returning a function as output, or both.
If a language does not have support for first-class functions, higher-order functions cannot exist in that language.
This means that a function retains access to variables that were in scope when it was defined, even if the function is invoked in a scope without those variables.
“The process of fixing a number of arguments to a function, producing another function of smaller arity.” — Wikipedia
“Translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.” — Wikipedia
The number of arguments a function takes.
Partial function application is a way of predefining some arguments to be applied first, before any subsequent arguments.
As a kind author in this Wikipedia article states:
“If you fix the first arguments of the function, you get a function of the remaining arguments.”
In this code sample, the function
partiallyApply takes a function
func and some predefined arguments (
argsToApply) as input, and returns a new function that wraps around
It’s in this wrapper function that the magic happens. When we create the anonymous wrapper function, a closure is created that retains access to the
This means that even when the wrapper function is returned and
argsToApply is no longer in scope, the wrapper function retains access to
Here are some partially applied functions in action:
As we can see, in the case of
concat5, we were able to predefine three of the five arguments and then later call the function with the remaining two, even though those predefined arguments are no longer in scope.
This is somewhat related to currying, which we will now explore.
Currying, though related to partial application, is not the same. To curry a function is to take a function and transform it into a sequence of functions, each taking one argument.
Here’s an example to illustrate:
There are a few interesting things going on here:
curryfunction takes a function as an argument and returns a new function.
- The returned function returns a function, and so on (this is the sequence of functions). If you were to inspect the return values for each of the curried function calls, you would see that they are all functions except for the very last return value, which brings us to this:
- The sequence of functions knows exactly when the last argument has been provided, and instead of returning a new function, it returns the final result.
As we can see, what sets currying apart from partial application is that currying returns a sequence of functions, compared to partial application’s single function, that knows when to finish and return the final result. This is because currying cares about the arity of a function.
If we remember the definition from the terms at the top of this article, arity is the number of arguments that a function takes. The
curry function is able to detect when the required number of arguments have been passed in and act accordingly.
Function.prototype.length method, as seen below:
Notice that default parameters and the rest parameter don’t count towards the
length of a function. In the context of currying, this makes sense. If the arguments are optional, how should we know when to return the final value instead of another function?
It’s also important to understand that
argsAfterDefault has a
length of a function, it only cares about the parameters before any default or rest parameters.
With that information, we now know a little bit more about how we want our
curry function to behave. It should only return the final value after
length arguments have been passed in — one at a time.
But what if we wanted to be able to pass in multiple at a time? Or not pass in any?
Consider this example:
Which implementation of the two curry functions do we want?
currySingleArgs strictly adheres to the definition of currying, but
curryMultipleArgs is much more flexible. It really comes down to what you need for your specific situation, so let’s go ahead and dig into both.
Currying With Multiple Arguments
Let’s analyze the implementation of these two curry functions, starting with
This may look a little bit complex, but once we break it down, it becomes simpler. Let’s take a look at the function that is returned:
This is the curried version of our passed-in function.
What’s important to note here is that like partial application, this inner (or “wrapper”) function creates a closure that can still access the original passed-in function to get its
length and eventually invoke it, even after the function is out of scope.
This function has two main parts:
- If the proper number of arguments has been passed in, invoke the original function with those arguments.
- Otherwise, return an anonymous function that will accept more arguments.
The first part is easier to understand. If we curry our function and then immediately invoke the curried function (
curry) with the correct number of arguments, the original function (
func) will be executed with those arguments.
The second part is where it gets interesting. Let’s analyze the anonymous function returned:
It’s a function that takes an arbitrary number of arguments, similar to the
curried function, that when invoked with those arguments, calls the
Again, as with the partial application example and with
curried, what’s important to note is that this anonymous function creates a closure that retains access to important references, namely the arguments passed into
Now, this is still a little bit unclear, but it will make a lot more sense when we step through how the program is executed step-by-step.
For this purpose, I’ve created a modified version of
curryMultipleArgs that logs the value of
args2 each time a function in the sequence of functions is executed:
This solution works by recursively calling
curried in a roundabout way, with an anonymous function that accepts more arguments.
If this doesn’t make 100% sense, don’t worry. It’s OK and to be expected. Currying is a challenging topic and there’s a lot to wrap your head around.
Currying With a Single Argument
Let’s analyze the implementation for
This implementation keeps track of all the arguments not by passing in arrays and concatenating with each function invocation, but by first initializing an array to hold all the arguments and then pushing a new argument to the array each time
intermediate is called.
There are two main steps to this algorithm:
- On the first function call in the sequence of functions (when
curriedis called), initialize the arguments array and immediately invoke
intermediatewith the passed-in argument.
funcif the correct number of arguments has been eventually passed in. If the correct number of arguments have yet to be passed in, return
intermediateto accept another argument.
Let’s take an inside-out approach to dissect this function, starting with the second step:
There’s not much going on in this snapshot. This function accepts one argument,
arg, and immediately pushes it to the
After that, if the length of
args is equal to the length of
func, which is true only when the very last argument has been passed in, we invoke
func with the arguments and return the result.
Otherwise, we return
intermediate, allowing the program to keep chaining the function calls.
There’s not too much going on in the second step, so let’s take a close look at the first step. It can be a little bit confusing, but once we break it down, we can start to understand what exactly is happening:
It is very important to note that the array of arguments is initialized when we call the first function in the sequence of functions, not when we curry the function.
Take, for example, this naive currying solution that initializes the arguments array when the function is curried:
To avoid this sort of behavior, we need to initialize
args after we start calling the curried function.
One way to do that would be to wrap the whole thing in a function, which we name
curried, and then invoke
intermediate immediately after initializing
That way, any subsequent calls of
intermediate will still be able to access the
args array but any new calls of the
curried function will have their own copy of
The only way to get an
intermediate function to add another argument to
args is for
curried or another
intermediate to return it, which forces us to chain our function calls and not rely on state:
As you can see, that’s all it takes to get the function to behave appropriately.
There are a lot of things going on here, but when we break down and analyze the solution in little pieces, it starts to make sense. Out of the many things happening, these are the most important to understand:
- By only accepting a single argument in the function signatures, we only deal with one argument at a time.
- That single argument, whether it is present or not, is pushed into our
argsarray. This is the array that will be applied to
funcwhen it has the correct number of arguments.
argsis only initialized after we start invoking the curried function, not before. This gives us easy, predictable behavior. We accomplish this with an IIFE and somewhat heavy use of closures.
This was a one-off article/guide. I hope you enjoyed it and found it helpful and interesting.