Currying is a way of building functions in a way that it is possible to call them with arguments one by one, instead of all of them at once. It will allow us to create new functions, which have some arguments already remembered. Currying helps with creating specialized functions based on general ones, ex. getName
, findId
, joinWithSpaces
from get
, find
, join
. It helps reducing code duplication.
Creating a curried function
Let me start with very simple example, sum
function in curried form:
function sum(a) {
return function(b) {
return a + b;
}
}
// using arrow function
const sum = a => b => a + b;
Looks strange at first sight, but let me explain it.
After calling sum(5)
it won't return a number, but a new function:
function(b) {
return 5 + b; // a was changed to 5
}
// using arrow function
b => 5 + b;
Only after we call it the second time we will get the final result - sum of two numbers.
function sum(a) {
return function(b) {
return a + b;
}
}
// using arrow function
const sum = a => b => a + b;
const addTo5 = sum(5); // function(b) { return 5 + b }
addTo5(3); // 8
// is the same as
add(5)(3); // 8
Summing up, we created specialized function addTo5
which has only one purpose - adding some number to 5. Function sum
is more general.
When to use the curried function
Let's see another curried function, which you might use in your application. Function get
will take key
name, return a function which takes object
and in the end return a value for provided key
. One use-case if using it as an argument for Array.map
function get(key) {
return function(obj) {
return obj[key];
}
}
// using arrow function
const get = key => obj => obj[key];
const getName = get('name');
const users = [{ name: 'John' }, { name: 'Emma' }];
const namesList = users.map(user => user.name); // normal way
const namesList = users.map(getName); // using specialized function
const namesList = users.map(get('name')) // using general function
As you can see this example is more practical. Because object
is the last argument, we can pass this "unfinished" function to map
, which calls this function with object. As a result we get value of name
from the object.
Notice how more readable is this example when using get
function - users.map(getName)
, you immediately understand what this code fragment does.
Good practices of creating curried functions
When creating your own curried function you should follow one important rule.
Always leave the source of data as the last argument. To determine the order of other "configuration" arguments, think about function use-cases.
Let's take a look at the get
function where source of data (object
) is NOT the last argument.
function get(obj) {
return function(key) {
return obj[key];
}
}
// using arrow function
const get = obj => key => obj[key];
const users = [{ name: 'Adam' }, { name: 'Maciej' }]
const namesList = users.map(user => get(user)('name'))
As you can see using get
function in this form doesn't make sense. And you can't really create a specialized function since you don't have access to the object yet. It is a good practice to leave the source of data as the last argument, because looking at the use-cases it usually comes at the end when executing the code.
Creating curried functions from standard function
In JavaScript world we are not used to curried functions, but looking at the use-cases it looks very promising. Actually we can create a function which is both curried and not.
function add(...args) {
if (args.length === 1) {
const [a] = args
return function(b) {
return a + b
}
}
const [a, b] = args
return a + b
}
add(5, 3) // 8
add(5)(3) // 8
If add
is called with one argument args.length === 1
, then we return new function where first argument is remembered, just like we did before. But if we call it with two arguments, it will just add them and return a sum.
You have to admit that it is very impressive, but the definition of this function is very complicated now and it's only summing up two numbers.
Thankfully there is a helper function which will help us with it:
function curry(argsLength, originalFunction) {
function next(prevArgs) {
function curriedFunction(nextArgs) {
const allArgs = [...prevArgs, ...nextArgs]
if (allArgs.length >= argsLength) {
// all arguments are provided, call the function
return originalFunction(...args);
}
else {
return next(allArgs)
}
}
}
return next([])
}
It looks even more complicated but with it, we can simply transform the standard function into a curried one. Just remember that the first argument of curry
is the number of arguments it can take, and the second one is the function itself;
const add = curry(2, (a, b) => a + b);
const addTo5 = add(5);
add(5, 3) // 8
addTo5(3); // 8
add(5)(3); // 8
Now you don't have to worry about how many arguments you call the function with. You call the function like you did before you got to know about currying or call the function, by one argument at a time.
I'm regularly publishing my insights on web development.
Consider subscribing to my newsletter.
Visit my blog at slawkolodziej.com to find out more interesting content.
Follow me on Twitter.
Top comments (4)
Awesome article! I'd add one thing: there are amazing libraries out there with a lot of useful functions ready to be used out of the box, for example ramda.
Yes! Ramda functions are curried out of the box.
Great snippets ! Do you mind creating these snippets in JSitor.com 🙂
It will be helpful to see live version of these snippets.
Very interesting!