Currying & Partial Application
In functional programming, Currying and Partial Application are advanced techniques for transforming and composing functions. While they are related and both rely heavily on JavaScript closures, they serve slightly different purposes.
1. Currying
Currying is the process of transforming a function that takes multiple arguments into a sequence of functions that each take a single argument.
A curried function does not execute immediately when provided with one argument. Instead, it returns a new function that expects the next argument, continuing this chain until all arguments are provided.
Standard Function
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
Curried Function
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
console.log(curriedAdd(1)(2)(3)); // 6
// Using ES6 Arrow Functions makes this much cleaner:
const curriedAddArrow = a => b => c => a + b + c;
console.log(curriedAddArrow(1)(2)(3)); // 6
Advanced: A Dynamic Curry Utility
You can write a helper function to convert any normal function into a curried version dynamically.
function curry(fn) {
return function curried(...args) {
// If enough arguments have been provided, execute the original function
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
// Otherwise, return a new function that waits for the rest of the arguments
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
}
}
};
}
const curriedSum = curry(function(a, b, c) {
return a + b + c;
});
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6 (Supports partial application dynamically)
2. Partial Application
Partial application is the process of fixing a number of arguments to a function, producing another function of smaller arity (taking fewer arguments).
Unlike currying, which transforms a function into a chain of unary (single-argument) functions, partial application just binds some arguments ahead of time and returns a function waiting for the rest.
The built-in Function.prototype.bind() is commonly used for this.
function calculateDiscount(discount, price) {
return price - (price * discount);
}
// Partially applying the 'discount' argument
const tenPercentOff = calculateDiscount.bind(null, 0.10);
const twentyPercentOff = calculateDiscount.bind(null, 0.20);
console.log(tenPercentOff(100)); // 90
console.log(twentyPercentOff(100)); // 80
3. Function Composition (compose and pipe)
Currying and partial application shine brightest when used alongside function composition. Composition allows you to combine simple functions to build more complicated ones.
compose (Right-to-Left)
Executes functions from right to left, passing the output of one as the input to the next.
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const toUpper = str => str.toUpperCase();
const exclaim = str => `${str}!`;
// exclaim runs first, then toUpper
const angryGreeting = compose(toUpper, exclaim);
console.log(angryGreeting("hello")); // "HELLO!"
pipe (Left-to-Right)
Executes functions from left to right. This is often more readable.
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
const add2 = x => x + 2;
const multiply3 = x => x * 3;
// add2 runs first, then multiply3
const calculate = pipe(add2, multiply3);
console.log(calculate(5)); // (5 + 2) * 3 = 21
Summary
- Currying: Transforms a function of
Narguments intoNfunctions of 1 argument.f(a, b) -> f(a)(b) - Partial Application: Pre-fills some arguments of a function and returns a new function taking the remaining arguments.
- Composition: Connecting multiple functions where the output of one becomes the input of the next. Currying makes it much easier to prepare functions for composition.