如何在TypeScript运行时自动检测并反柯里化柯里化函数?
TypeScript中柯里化函数的自动检测与反柯里化实现
问题描述
需要实现一套TypeScript运行时方案,自动检测一个函数是否为每次接收单个参数并返回新函数的柯里化函数,若符合条件则将其反柯里化为可一次性接收所有参数的普通函数。
例如:
原柯里化函数:
const curriedAdd = (a: number) => (b: number) => (c: number) => a + b + c;
转换后得到:
const add = (a: number, b: number, c: number) => a + b + c;
此前尝试的基础启发式检测方法无法处理多参数或复杂结构的柯里化函数,需要支持简单及复杂场景的可靠解决方案。
核心思路
- 柯里化检测:递归判断函数接收单个参数后是否返回新函数,同时加入边界控制避免无限递归(比如限制递归深度)。
- 反柯里化转换:构建一个包装函数,收集传入的所有参数,递归调用柯里化函数直到返回值不再是函数,最终返回计算结果。
- 类型推断:利用TypeScript的条件类型递归推断柯里化函数的参数列表,为反柯里化后的函数提供准确的类型定义。
实现代码
类型定义
首先定义用于推断反柯里化后函数类型的工具类型:
// 递归提取柯里化函数的所有参数类型 type CurriedParameters<T> = T extends (...args: infer Args) => infer Return ? Args extends [infer First] ? [First, ...CurriedParameters<Return>] : [] : []; // 定义反柯里化后的函数类型 type DecurriedFunction<T> = (...args: CurriedParameters<T>) => ReturnType<ReturnType<T extends (...args: any[]) => any ? T : never>>;
运行时反柯里化函数
实现包含检测与转换逻辑的decurry函数:
function decurry<T extends (...args: any[]) => any>(fn: T, maxDepth = 10): DecurriedFunction<T> { // 检测函数是否为柯里化函数的辅助函数 const isCurried = (func: any, depth: number): boolean => { if (depth <= 0) return false; // 检查函数是否接收单个参数 if (func.length !== 1) return false; try { // 传入占位参数(避免触发副作用,若函数依赖参数类型则可能失效) const result = func(undefined); // 若返回值是函数,则递归检测 return typeof result === 'function' ? isCurried(result, depth - 1) : true; } catch { // 执行出错则判定为非柯里化函数 return false; } }; // 非柯里化函数直接返回原函数 if (!isCurried(fn, maxDepth)) { return fn as unknown as DecurriedFunction<T>; } // 构建反柯里化包装函数 return function(...args: any[]) { let currentFn: any = fn; for (const arg of args) { currentFn = currentFn(arg); // 返回值非函数则提前终止 if (typeof currentFn !== 'function') break; } return currentFn; } as DecurriedFunction<T>; }
测试示例
基础加法示例
const curriedAdd = (a: number) => (b: number) => (c: number) => a + b + c; const add = decurry(curriedAdd); console.log(add(1, 2, 3)); // 输出 6 console.log(add(1)(2)(3)); // 仍支持柯里化调用(可选特性)
复杂类型示例
const curriedUserBuilder = (name: string) => (age: number) => (isActive: boolean) => ({ name, age, isActive }); const userBuilder = decurry(curriedUserBuilder); console.log(userBuilder('Alice', 30, true)); // 输出 { name: 'Alice', age: 30, isActive: true }
非柯里化函数兼容
const normalAdd = (a: number, b: number) => a + b; const wrappedAdd = decurry(normalAdd); console.log(wrappedAdd(1, 2)); // 输出 3(直接返回原函数)
注意事项
- 副作用处理:检测阶段传入
undefined作为参数可能触发函数内部的副作用,若函数对参数类型有严格校验,检测可能失败,此时可手动指定maxDepth或跳过检测直接转换。 - 动态返回逻辑:若函数根据传入参数动态决定是否返回新函数(比如某些参数下返回值不是函数),自动检测可能不准确,这种场景建议手动标记柯里化函数。
- 递归深度限制:默认
maxDepth=10可避免因无限递归函数导致的栈溢出,可根据实际需求调整。
内容的提问来源于stack exchange,提问作者Sinan Ismail
相关产品推荐
相关产品推荐

