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.
Very interesting!
Great snippets ! Do you mind creating these snippets in JSitor.com 🙂
It will be helpful to see live version of these snippets.