Function Currying: The Only Curry My Mom Can't Stop Me From Making
Rohit Nandi
React.js | React-native | Javascript | Typescript | Node.js | Redux | Jest | Docker
Function currying is a fundamental concept in functional programming and has become an essential tool for JavaScript developers. It transforms a function with multiple arguments into a sequence of functions, each taking a single argument. By doing so, currying allows for greater modularity, reusability, and readability in code, enhancing both development and debugging processes.
Named after the mathematician Haskell Curry, currying simplifies complex operations and makes functions more versatile, especially when paired with other programming paradigms like functional composition and higher-order functions.
What is Function Currying?
Function currying transforms the way we handle arguments in JavaScript functions. Instead of a single function call that takes all arguments at once, currying breaks the function into a chain of functions, each handling a single argument.
Example: Standard Function vs. Curried Function
Consider a standard function that takes multiple arguments:
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // Output: 6
A curried version of this function would look like:
function add(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(add(1)(2)(3)); // Output: 6
Each function in the chain takes one argument and returns another function until all arguments have been provided, at which point the original operation is executed.
Modern JavaScript Currying with Arrow Functions
Arrow functions allow for more concise syntax, making curried functions easier to write and read:
const add = a => b => c => a + b + c;
console.log(add(1)(2)(3)); // Output: 6
Benefits of Currying
1. Enhanced Modularity
Currying breaks down complex functions into smaller, reusable units. Each curried function handles a single concern, which improves code clarity and maintainability.
2. Partial Application
Currying enables partial application, where some arguments are pre-set, creating specialized versions of a function. This is useful when certain arguments are constant across multiple calls:
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // Output: 10
console.log(double(8)); // Output: 16
3. Function Composition
Currying simplifies composing smaller functions into larger, more complex operations. This is especially useful in functional programming:
const compose = f => g => x => f(g(x));
const addOne = x => x + 1;
const double = x => x * 2;
const addOneThenDouble = compose(double)(addOne);
console.log(addOneThenDouble(3)); // Output: 8
4. Event Handling
Currying makes it easier to pass additional parameters to event handlers:
const handleEvent = eventType => selector => callback => {
document.querySelector(selector).addEventListener(eventType, callback);
};
const handleClick = handleEvent('click');
const handleButtonClick = handleClick('#myButton');
handleButtonClick(e => console.log('Button clicked!'));
Implementation Techniques
领英推荐
1. Using Closures
Closures are the backbone of currying, allowing inner functions to access the arguments provided to outer functions:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return (...nextArgs) => curried(...args, ...nextArgs);
}
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // Output: 6
console.log(curriedSum(1, 2)(3)); // Output: 6
2. Using bind Method
The bind() method can be used to partially apply arguments to a function, effectively creating a curried function:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(4)); // Output: 8
3. Using Arrow Functions
Arrow functions provide an elegant way to implement currying without relying on closures explicitly:
const curry = a => b => c => a + b + c;
console.log(curry(1)(2)(3)); // Output: 6
Advanced Applications of Currying
1. Dynamic DOM Selector Builder
Currying can dynamically generate DOM selectors:
const get = element => ({
withAttribute: attribute => ({
andValue: value => `${element}[${attribute}="${value}"]`
})
});
const selector = get('input').withAttribute('data-testid').andValue('username');
console.log(selector); // Output: input[data-testid="username"]
2. Partial Application for API Calls
Pre-configure API calls with currying for cleaner code:
const fetchFromAPI = baseURL => endpoint => options => {
return fetch(`${baseURL}${endpoint}`, options);
};
const fetchFromMyAPI = fetchFromAPI('https://api.myservice.com');
const fetchUsers = fetchFromMyAPI('/users');
const getUsers = fetchUsers({ method: 'GET' });
3. Infinite Argument Accumulator
Create a function to accumulate arguments until a terminating call is made:
const sum = a => {
let total = a;
const add = b => {
if (b === undefined) return total;
total += b;
return add;
};
return add;
};
console.log(sum(1)(2)(3)(4)()); // Output: 10
4. Memoization
Combine currying with memoization to optimize expensive function calls:
const memoize = fn => {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (key in cache) return cache[key];
cache[key] = fn(...args);
return cache[key];
};
};
const slowAdd = (a, b) => {
console.log('Computing...');
return a + b;
};
const memoizedAdd = memoize(slowAdd);
console.log(memoizedAdd(2, 3)); // Output: Computing... 5
console.log(memoizedAdd(2, 3)); // Output: 5 (from cache)
5. Validation Chains
Use currying to create reusable validation logic:
const validate = rules => value => {
return rules.reduce((errors, rule) => {
const result = rule(value);
return result ? errors : [...errors, result];
}, []);
};
const required = value => value && value.length > 0;
const minLength = min => value => value.length >= min;
const validatePassword = validate([required, minLength(8)]);
console.log(validatePassword('abc')); // Output: [false]
console.log(validatePassword('strongpassword')); // Output: []
Challenges and Considerations
While currying offers significant benefits, it also introduces challenges:
To mitigate these challenges, use currying judiciously and ensure that the intent of the curried functions is clear.