Currying is a technique that transforms a function with multiple parameters into a sequence of functions, each with a single parameter.
In other words, instead of passing all arguments to the function at once, currying allows you to “break down” the function into simpler steps.
Currying is a concept that comes from the mathematical field and has found an important place in functional programming. In JavaScript, it has become a very popular pattern.
This might seem complex at first. But, in some cases, this approach can improve modularity and code reusability.
Currying in JavaScript
To illustrate currying, let’s consider a simple example. Imagine we have a simple function that adds two numbers:
function add(a, b) {
return a + b;
}
Nothing particularly exciting, right? Let’s see the curried version of this function.
function curriedAdd(a) {
return function(b) {
return a + b;
};
}
Now, instead of calling our function add(a, b), we have a function that accepts one parameter and returns another function that accepts another parameter.
We can use our curriedAdd version as follows:
const add5 = curriedAdd(5);
console.log(add5(3)); // Output: 8
console.log(add5(10)); // Output: 15
In this example,
curriedAddtakes an argumentaand returns a new function that expects the second argumentb.- This allows us to create specialized functions (like
add5) that always add a fixed number.
That is, we now have a function that generates functions that add a specific number.
Currying with Lambdas
We can use lambda functions to create a more compact version of currying. The behavior remains the same, but the use of lambdas makes it more concise and more in line with modern JavaScript style.
const add = a => b => a + b;
const result = add(2)(3); // Result: 5
In this example,
- The
addfunction is now defined as a series of nested functions using arrows. - Each function returns another function that accepts the next argument, until the final calculation
a + bis reached.
Currying Implementation
We have seen how to manually convert a function into a “curried” version, as in the previous example with the add function.
However, it’s more common to create some generic solution to convert any function. Let’s look at some of them.
Utility Function
Currying can be implemented with a simple utility function. For example:
function currying(fn) {
return function(arg1) {
return function(arg2) {
return fn(arg1, arg2);
};
};
}
// Usage
function multiply(a, b) {
return a * b;
}
const curriedMultiply = currying(multiply);
const multiplyBy2 = curriedMultiply(2);
console.log(multiplyBy2(4)); // Output: 8
In this example, currying converts a function with two arguments into a sequence of unary functions.
Generic Utility Function
We can also create a utility function for more advanced currying that handles an arbitrary number of arguments.
function currying(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return function(...args2) {
return curried(...args, ...args2);
};
}
};
}
// Usage
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = currying(multiply);
const multiplyBy2and3 = curriedMultiply(2)(3);
console.log(multiplyBy2and3(4)); // Output: 24
In this example, the currying function takes any function fn and returns a curried version of it. This allows arguments to be passed one at a time or in groups.
Using Libraries
Many JavaScript libraries and frameworks provide support for currying. For example, Lodash has a _.curry function that makes it easy to create curried functions.
const _ = require('lodash');
const curriedSum = _.curry(function(a, b, c) {
return a + b + c;
});
console.log(curriedSum(1)(2)(3)); // 6
Function Composition
Currying and function composition are closely related. While currying decomposes functions into a series of unary functions, function composition allows combining them to build new, more complex functions.
const add = a => b => a + b;
const multiply = a => b => a * b;
// We combine the functions to create a new one
const multipliedSum = (a, b, c) => multiply(a)(add(b)(c));
console.log(multipliedSum(2, 3, 4)); // Output: 14
In this example,
addandmultiplyare curried functions that return a new function upon receiving an argument.multipliedSumuses composition. First,add(b)(c)calculates the sum ofbandc, returning7. Then,multiply(a)(7)multiplies the result bya, giving14.
That is, we have composed two curried functions (add and multiply) to form a new, more complex function (multipliedSum).
In a way, function composition is the inverse step of currying: instead of dividing, we are combining.
Practical Examples
Discount Calculation
Let’s see a practical example of how we could use currying in a real application:
function discount(percentage) {
return function(price) {
return price * (1 - percentage);
};
}
const applyDiscount10 = discount(0.1);
const applyDiscount20 = discount(0.2);
console.log(applyDiscount10(100)); // Result: 90
console.log(applyDiscount20(100)); // Result: 80
In this example, discount is a curried function that takes a discount percentage and returns another function that takes the price and calculates the price with the discount applied.
Function Configuration
Currying is useful for configuring functions with default arguments.
function configureGreeting(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const formalGreeting = configureGreeting('Good morning');
console.log(formalGreeting('John')); // Good morning, John!
Function Composition
Currying facilitates function composition, where several functions are combined to form a new function.
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
const curriedSum = currying(add);
const curriedMultiply = currying(multiply);
const result = curriedMultiply(curriedSum(2)(3))(4);
console.log(result); // 20
In this example, curriedSum(2)(3) returns 5, and curriedMultiply(5)(4) returns 20.
