13.2 柯里化(Currying)与函数组合
柯里化(Currying)
柯里化是一种将多参数函数转换为一系列单参数函数的技术,这是函数式编程中的重要概念。柯里化后的函数可以更灵活地进行组合和复用。
基本概念
柯里化得名于数学家 Haskell Curry,它指的是将一个接受多个参数的函数转换为一系列只接受一个参数的函数的过程。
// 普通函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化后的函数
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(add(1, 2, 3)); // 6
console.log(curriedAdd(1)(2)(3)); // 6
柯里化的实现
我们可以编写一个通用的柯里化函数:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
柯里化的优点
- 参数复用:可以固定某些参数,生成更专用的函数
- 延迟执行:可以分步传入参数,直到所有参数都准备好才执行
- 函数组合:便于进行函数组合(后面会讲到)
// 参数复用示例
function log(date, importance, message) {
console.log(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
const logCurried = curry(log);
// 创建当前时间的专用日志函数
const logNow = logCurried(new Date());
logNow("INFO", "message"); // [HH:mm] INFO message
// 创建当前时间的调试专用函数
const debugNow = logNow("DEBUG");
debugNow("some debug"); // [HH:mm] DEBUG some debug
现代JavaScript中的柯里化
在ES6+中,我们可以使用箭头函数更简洁地实现柯里化:
const curry = fn =>
curried = (...args) =>
args.length >= fn.length
? fn(...args)
: (...more) => curried(...args, ...more);
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
函数组合(Function Composition)
函数组合是将多个函数合并成一个新函数的过程,新函数的输出是前一个函数的输入。
基本概念
数学中的函数组合表示为:(f ∘ g)(x) = f(g(x))
在JavaScript中可以实现为:
const compose = (f, g) => x => f(g(x));
const toUpperCase = str => str.toUpperCase();
const exclaim = str => str + '!';
const shout = compose(exclaim, toUpperCase);
console.log(shout('hello')); // "HELLO!"
多函数组合
我们可以实现一个支持多个函数的组合工具:
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
// 或者使用箭头函数
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = compose(square, double, add1);
console.log(transform(2)); // ((2 + 1) * 2)^2 = 36
管道(Pipe)
管道是组合的反向操作,从左到右执行函数:
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const transformPipe = pipe(add1, double, square);
console.log(transformPipe(2)); // (((2 + 1) * 2)^2 = 36
柯里化与组合的结合
柯里化和函数组合经常一起使用,可以创建高度可复用的函数:
const map = curry((fn, arr) => arr.map(fn));
const filter = curry((fn, arr) => arr.filter(fn));
const reduce = curry((fn, init, arr) => arr.reduce(fn, init));
// 组合使用
const sumOfEvenSquares = pipe(
filter(x => x % 2 === 0),
map(x => x * x),
reduce((a, b) => a + b, 0)
);
console.log(sumOfEvenSquares([1, 2, 3, 4, 5])); // 20 (4 + 16)
实际应用示例
- 数据处理管道:
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 35, active: true }
];
const getActiveUsers = filter(user => user.active);
const getUserNames = map(user => user.name);
const createWelcomeMessage = map(name => `Welcome, ${name}!`);
const welcomeActiveUsers = pipe(
getActiveUsers,
getUserNames,
createWelcomeMessage
);
console.log(welcomeActiveUsers(users));
// ["Welcome, Alice!", "Welcome, Charlie!"]
- 日志处理:
const trace = label => value => {
console.log(`${label}: ${value}`);
return value;
};
const cleanInput = str => str.trim();
const toSlug = str => str.replace(/\s+/g, '-').toLowerCase();
const slugify = pipe(
cleanInput,
trace('after clean'),
toSlug,
trace('after slugify')
);
console.log(slugify(' Hello World '));
// after clean: Hello World
// after slugify: hello-world
// hello-world
总结
柯里化和函数组合是函数式编程中的两个强大工具:
-
柯里化:
- 将多参数函数转换为一系列单参数函数
- 支持参数复用和延迟执行
- 便于创建更专用的函数版本
-
函数组合:
- 将多个函数合并成一个新函数
- 数据流从右到左(compose)或从左到右(pipe)
- 创建清晰的数据处理管道
这些技术可以帮助我们编写更简洁、更模块化、更易于维护的代码。在实际开发中,它们特别适合数据处理、转换和流水线操作场景。
#前端开发
分享于 2025-03-25
上一篇:13.1 纯函数与副作用
下一篇:13.3 高阶函数与闭包应用