JavaScript Currying Explained: Partial Applications and Beyond
Currying is a functional programming technique that transforms a function accepting multiple arguments into a sequence of functions, each taking a single argument. It allows you to create a series of "partial applications" of the original function, where each application captures a portion of the arguments and returns a new function that expects the remaining arguments.
How Currying Works
Here's how currying works in JavaScript, leveraging closures:
function add(a, b) { return a + b; }
Create a Currying Function: You write a new function that takes the first argument and returns a nested function. The nested function captures the first argument using a closure and takes the remaining arguments:
function curry(fn) { return function(a) { return function(b) { return fn(a, b); }; }; }
const addCurried = curry(add);
Partial Applications: Call the curried function with the first argument:
const add5 = addCurried(5); // This captures 5 as the first argument
Now,
add5
is a new function that expects the second argument:const result = add5(3); // This provides the second argument (3) console.log(result); // Output: 8
Benefits of Currying
- Partial Application: As demonstrated above, currying allows you to create pre-configured functions with some arguments fixed, promoting code reusability.
- Functional Composition: Curried functions can be easily combined with other functions to create more complex operations, aligning well with functional programming principles.
- Flexibility: Currying provides flexibility in how you provide arguments to a function.
Terminology:
- Functional Programming: A programming paradigm that emphasizes functions as first-class citizens, treating them as values that can be passed around, stored in variables, and returned from other functions.
- Closure: A function that has access to the variable environment of its outer (enclosing) function, even after the outer function has returned. In currying, the closure captures the first argument passed to the curried function.
function add(a, b) {
return a + b;
}
function curry(fn) {
return function(a) {
return function(b) {
return fn(a, b);
};
};
}
const addCurried = curry(add);
const add5 = addCurried(5);
const result1 = add5(3); // Output: 8
const add10 = addCurried(10);
const result2 = add10(20); // Output: 30
This code defines the add
function, then uses a curry
function to create a curried version (addCurried
). You can then call addCurried
with the first argument, resulting in a new function that expects the second argument.
More Advanced Currying:
function multiply(a, b, c) {
return a * b * c;
}
function curry(fn, arity = fn.length) {
// `arity` keeps track of the expected number of arguments
return function curried(arg) {
const args = [arg]; // Store arguments in an array
return function inner(nextArg) {
if (args.length === arity) {
return fn(...args); // Apply the original function with all arguments
} else {
args.push(nextArg);
return inner; // Return itself to capture the next argument
}
};
};
}
const multiplyCurried = curry(multiply);
const multiplyBy5 = multiplyCurried(5);
const result3 = multiplyBy5(3)(2); // Output: 30
const startWith4 = multiplyCurried(4);
const result4 = startWith4(2)(7); // Output: 56
This example enhances the curry
function to keep track of the expected number of arguments (arity
) using a default parameter. It also uses an array to store the arguments progressively. This allows currying functions that take more than two arguments.
The built-in bind
method allows you to create a new function with a pre-defined set of arguments for another function. This is similar to partial application achieved through currying.
Here's an example:
function add(a, b) {
return a + b;
}
const add5 = add.bind(null, 5); // Pre-set first argument to 5
const result = add5(3); // Only provide the second argument
console.log(result); // Output: 8
In this approach, bind
creates a new function add5
that internally holds the value 5 for the first argument. When you call add5(3)
, you only need to provide the second argument.
Function Composition:
Functional programming heavily utilizes function composition, where functions are chained together to create complex operations. You can achieve similar results to currying by composing functions.
function add5(x) {
return x + 5;
}
function multiply(y) {
return function(x) {
return x * y;
};
}
const multiplyBy7 = multiply(7);
const result = multiplyBy7(add5(10)); // (10 + 5) * 7
console.log(result); // Output: 105
In this approach, add5
adds 5 to its argument. multiply
creates a new function that takes one argument and multiplies it by a pre-defined value (captured in the closure). Chaining these functions achieves a similar outcome to a curried function.
Choosing the Right Method:
- Currying: Suitable when you need to create pre-configured functions with varying argument combinations used frequently. It emphasizes code reusability and promotes a functional style.
- Partial Application (
bind
): Simpler approach for situations where you only need to fix a few arguments and don't require a full currying setup. - Function Composition: Powerful for building complex operations by chaining functions together, offering flexibility in how you construct the logic flow.
javascript functional-programming terminology