功能说明
curry 把接收多个参数的函数转换为每次可传部分参数的版本,参数足够时才真正调用原函数。占位符常量允许先跳过某个位置,用后续调用的参数填补。uncurry 则把一串单参数调用还原为一次性传参。适合构造可复用的事件处理器、配置化转换函数、函数组合以及测试中的部分应用。
关键代码
柯里化依赖函数的 length 属性推断参数个数,也允许显式传入 arity 以处理默认参数或剩余参数。每次调用先截取所需参数数量,若已足够且不含占位符就通过 apply 保留 this 并执行原函数。否则返回收集器,把占位符按顺序替换为新参数,多余参数追加到末尾,再递归进入柯里化函数。占位符使用 Symbol,避免与普通参数冲突。
使用注意
函数的 length 不包含默认值和剩余参数,这类函数应明确传入 arity,否则可能过早执行或永远无法满足。占位符未被填补时会一直返回新函数,调用方应避免无限累积。每次部分应用都会创建新数组,极端热路径需评估分配开销。该方法不改变原函数,也不做参数类型校验,类型安全应交给 TypeScript 重载。
验证方式
测试一次传满参数、逐个传参、先传两个再传一个以及带占位符的乱序传参,断言结果一致。验证 this 绑定在方法和普通函数中的表现。测试 arity 为零、默认参数函数显式指定 arity、非法 arity 与非函数输入。再用 uncurry 还原柯里化函数,确认多参数调用得到相同结果。
代码示例
export const curryPlaceholder = Symbol("curry.placeholder");
export function curry(fn, arity = fn.length) {
if (typeof fn !== "function") throw new TypeError("fn must be a function");
if (!Number.isInteger(arity) || arity < 0) throw new RangeError("arity must be a non-negative integer");
return function curried(...args) {
const supplied = args.slice(0, arity);
if (supplied.length === arity && !supplied.includes(curryPlaceholder)) {
return fn.apply(this, supplied);
}
return function next(...more) {
const merged = [];
let cursor = 0;
for (const argument of args) {
if (argument === curryPlaceholder && cursor < more.length) {
merged.push(more[cursor]);
cursor += 1;
} else {
merged.push(argument);
}
}
while (cursor < more.length) {
merged.push(more[cursor]);
cursor += 1;
}
return curried.apply(this, merged);
};
};
}
export function uncurry(curried) {
if (typeof curried !== "function") throw new TypeError("curried must be a function");
return (...args) => args.reduce((fn, argument) => fn(argument), curried);
}