柯里化与偏函数
柯里化是什么?
- 柯里化 (currying): 把可按
f(a, b, c)调用的函数, 变换成可按f(a)(b)(c)调用的形式; - 只变换: 柯里化本身不调用函数, 只做转换;
- 偏函数 (partial): 固定了部分参数后得到的函数, 如
log(new Date()); - JS 特色: 主流实现是增强版 —— 转换后既支持一次传全, 也支持分段传参;
两参柯里化怎么手写?
function curry(f) {
return function (a) {
return function (b) {
return f(a, b);
};
};
}
function sum(a, b) {
return a + b;
}
const curriedSum = curry(sum);
curriedSum(1)(2); // 3
- 返回值:
curry(func)返回包装function (a); - 第一层:
curriedSum(1)把1存进闭包, 返回新包装function (b); - 第二层:
curriedSum(1)(2)把两个参数一起交给原函数sum; - 机制: 依赖闭包保存已收参数, 见 闭包;
柯里化有什么用?
以三参日志函数为例:
function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
log = _.curry(log);
log(new Date(), "DEBUG", "some debug"); // 正常调用
log(new Date())("DEBUG")("some debug"); // 柯里化形式
const logNow = log(new Date()); // 固定第一个参数 → 偏函数
logNow("INFO", "message"); // [HH:mm] INFO message
const debugNow = logNow("DEBUG"); // 继续固定
debugNow("message"); // [HH:mm] DEBUG message
- 结论 1: 柯里化不损失能力,
log照常可调用; - 结论 2: 固定环境参数后可批量生产语义化短函数 (今日日志、今日 DEBUG 日志);
- 模式: 通用函数 → 固定环境 / 日期 / 通道 → 专用函数;
怎样实现支持任意参数个数的 curry?
- 增强版: lodash 的
_.curry这类实现同时兼容curriedSum(1, 2)与curriedSum(1)(2); - 判据: 参数够就直接执行原函数, 不够才返回偏函数;
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
}
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
curriedSum(1, 2, 3); // 6, 正常调用
curriedSum(1)(2, 3); // 6, 只柯里化第一个参数
curriedSum(1)(2)(3); // 6, 完全柯里化
- 分支一: 已收参数数量 ≥
func.length时, 直接func.apply(this, args)转交; - 分支二: 否则不调用
func, 返回新包装, 把旧参数与新参数concat后递归调用curried; this: 用apply(this, ...)透传, 方法柯里化后 this 语义不丢, 见 call、apply、bind 与手写实现;
curry 有哪些限制?
- 定长函数: 依赖
func.length判断参数是否收齐, 用剩余参数的f(...args)无法这样柯里化; - 定义与实现有偏差: 严格按定义
f(a, b, c)只能变成f(a)(b)(c), 而 JS 实现通常额外保留多参调用; - 偏函数不是新语义: 它只是"已经收了一部分参数的柯里化函数", 没有独立的类型;