Function Currying in JavaScript
Function currying is a technique that transforms a function using multiple parameters into a series of functions that each take a single parameter. Its core idea is to convert a multi-parameter function into multiple single-parameter functions, essentially breaking down a function into multiple steps, where each step processes only one parameter.
Basic Concepts and Principles
The name "currying" comes from the mathematician Haskell Curry. In JavaScript, a curried function receives some parameters, then returns a new function to handle the remaining parameters until all parameters are collected before executing the original function.
Detailed Implementation Steps
- Basic Currying Implementation
Let's first look at a simple manual currying example:
// Original function
function add(a, b, c) {
return a + b + c;
}
// Manual currying version
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
// Usage
console.log(curriedAdd(1)(2)(3)); // 6
- General-Purpose Currying Function Implementation
Now let's implement a generalcurryfunction that can curry any function:
function curry(fn) {
return function curried(...args) {
// If enough arguments are provided, execute the original function directly
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// If not enough arguments, return a new function to receive the remaining arguments
else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
}
}
}
}
// Usage example
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
- Advanced Currying with Placeholder Support
A more advanced implementation can support placeholders for more flexible argument passing:
function curryWithPlaceholder(fn) {
return function curried(...args) {
// Filter out placeholders to check the number of valid arguments
const validArgs = args.filter(arg => arg !== curryWithPlaceholder.placeholder);
if (validArgs.length >= fn.length && args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
// Handle placeholder replacement
const combinedArgs = args.map(arg =>
arg === curryWithPlaceholder.placeholder && nextArgs.length
? nextArgs.shift()
: arg
).concat(nextArgs);
return curried.apply(this, combinedArgs);
}
}
}
curryWithPlaceholder.placeholder = Symbol('placeholder');
// Usage example
const _ = curryWithPlaceholder.placeholder;
const curriedFunc = curryWithPlaceholder(function(a, b, c, d) {
return a + b + c + d;
});
console.log(curriedFunc(1, _, 3)(2)(4)); // 10 (1 + 2 + 3 + 4)
Practical Application Scenarios
- Parameter Reuse
// Create a general validation function
function check(reg, text) {
return reg.test(text);
}
// Create specific validators after currying
const curriedCheck = curry(check);
const checkPhone = curriedCheck(/^1[3456789]\d{9}$/);
const checkEmail = curriedCheck(/^\w+@\w+\.\w+$/);
console.log(checkPhone('13800138000')); // true
console.log(checkEmail('test@example.com')); // true
- Delayed Execution
// Advantages in event handling
const curriedLog = curry(function(level, time, message) {
console.log(`[${level}] ${time}: ${message}`);
});
const infoLog = curriedLog('INFO', new Date().toISOString());
const errorLog = curriedLog('ERROR', new Date().toISOString());
// In actual business logic, only the message needs to be passed
infoLog('User login successful');
errorLog('Database connection failed');
Advantages and Considerations of Currying
Advantages:
- Parameter reuse, improving function applicability
- Delayed execution, increasing code flexibility
- More convenient function composition
Considerations:
- Currying increases function call nesting, which may impact performance
- Not all scenarios are suitable for currying; choose based on actual needs
- The calling style of curried functions may not align with traditional habits
By understanding the principles and implementations of function currying, you can engage in functional programming more effectively, writing more modular and reusable code.