所以我有一个柯里化其他函数的函数:
const curry = <TArg, TReturn>(fn: (...args: TArg[]) => TReturn) => {
const curried = (...args: TArg[]) =>
args.length < fn.length
? (...innerArgs: TArg[]) => curried(...args, ...innerArgs)
: fn(...args);
return curried;
};
const join = (a: number, b: number, c: number) => {
return `${a}_${b}_${c}`;
};
const curriedJoin = curry(join);
curriedJoin(1)(2, 3); // Should return '1_2_3', but gives an error
Typescript 不允许我多次调用它,因为第一次调用可能返回一个字符串。你会如何解决它?
唯一可行的方法是
curry()
返回一个函数,其输出类型很大程度上取决于输入函数中的参数数量和传入的参数数量。这意味着您不仅需要它是 generic ,但也可以使用条件类型来表达差异。并且您需要该函数的类型是递归的,因此我们必须给它一个名称:
interface Curried<A extends any[], R> {
<AA extends Partial<A>>(...args: AA):
A extends [...{ [I in keyof AA]: any }, ...infer AR] ?
[] extends AR ? R : Curried<AR, R> : never;
}
因此
Curried<A, R>
表示参数列表为 A
且返回类型为 R
的函数的柯里化版本。您可以使用通用剩余参数类型 args
的一些 AA
来调用它,它必须可分配给 A
的部分版本(使用 Partial<T>
实用程序类型)。然后我们需要找出参数列表的rest,即infer
红色为AR
(请注意,也许A extends [...AA, ...infer AR] ? ⋯
可以工作,但如果由于某种原因AA
比A
的初始部分,那会失败。所以{[I in keyof AA]: any}
只是意味着“与AA
长度相同的任何东西”)。如果 AR
为空 ([]
),则柯里化函数返回 R
。否则它返回 Curried<AR, R>
,这样生成的函数也可以被调用。
实现可能如下所示:
const curry = <A extends any[], R>(fn: (...args: A) => R) => {
const curried = (...args: any): any =>
args.length < fn.length
? (...innerArgs: A[]) => curried(...args, ...innerArgs)
: fn(...args);
return curried as Curried<A, R>;
};
any
类型和类型断言来说服编译器curry
的实现是可以接受的。编译器无法真正理解许多高阶泛型类型操作,或验证哪些函数实现可能满足它们。
让我们测试一下:
const join = (a: number, b: number, c: number) => {
return `${a}_${b}_${c}`;
};
const curriedJoin = curry(join);
// const curriedJoin: Curried<[a: number, b: number, c: number], string>
const first = curriedJoin(1);
// const first: Curried<[b: number, c: number], string>
const s = first(2, 3);
// const s: string
console.log(s); // "1_2_3"
curriedJoin(1)(2, 3); // '1_2_3'
看起来不错,一切都按预期运行。
length
函数属性 的代码都无法在 TypeScript 的类型系统中完美表示。 TypeScript 的立场是,可以使用比参数数量更多的参数安全地调用函数,至少在回调的可分配性方面是这样。请参阅文档和常见问题解答。因此,您可以拥有一个 length
与 TypeScript 所了解的参数数量不一致的函数。这意味着您可能会陷入这种情况:
function myFilter(predicate: (value: string, index: number, array: string[]) => unknown) {
const strs = ["a", "bc", "def", "ghij"].filter(predicate);
console.log(strs);
const curried = curry(predicate);
curried("a")(2);
}
myFilter()
接受字符串数组的 filter()
方法 接受的类型的回调。该回调将使用三个参数调用;这就是 filter()
在 JavaScript 中的工作原理。但 TypeScript 允许您传递接受更少参数的回调。所以你可以这样称呼:
myFilter(x => x.length < 3) // ["a", "bc"], followed by RUNTIME ERROR!
任何地方都没有编译器错误,但会出现运行时错误。
myFilter
的主体认为回调的长度为3
,但在运行时它的长度确实为1
。哎呀。
这可能不会在您的实际代码中发生,但您应该意识到这一点。