您并没有真正在代码中使用variadic tuple tpes。您正在实现的 curry() 的特定风格是采用部分参数列表,然后可能返回另一个函数,该函数采用列表的部分剩余部分。这意味着参数 T 的初始元组可能会被分成许多不同的部分,因此不会像文档中的 partialCall() 函数那样从上下文中自动推断出来。
相反,您需要将T 元组显式分解为可能的子元组。让我们表示curry() 所需输出的类型,它接受一个函数,其参数是元组A,其返回类型是R:
type Curried<A extends any[], R> =
<P extends Partial<A>>(...args: P) => P extends A ? R :
A extends [...SameLength<P>, ...infer S] ? S extends any[] ? Curried<S, R>
: never : never;
type SameLength<T extends any[]> = Extract<{ [K in keyof T]: any }, any[]>
让我把它翻译成英文。 Curried<A, R> 是一个泛型函数,其参数必须是某种元组类型 P,它被限制为 Partial<A>。
对于元组,Partial<A> 最终意味着您可以省略元组的任何后缀(从某个地方到结尾)。所以[1, 2, 3] 可以分配给Partial<[1,2,3,4,5,6,7]>,但[1, 2, 4] 不是。 undefined 有一点问题,因为 [1, undefined, 3] 也可分配给 Partial<[1,2,3,4,5,6,7]>,但我将忽略这一点,如果它变得重要,它可以解决。无论如何,这意味着Curried<A, R> 的参数必须是A 元组的某个前缀(初始块)。
Curried<A, R> 的返回类型取决于传入的前缀P。如果P 是整个元组A,那么返回类型就是R(这就是当你终于给定函数的所有参数)。否则,将A 拆分为前缀P 及其后缀S,并返回Curried<S, R> 类型的新柯里化函数。
将A 拆分为[...SameLength<P>, ...infer S] 使用可变元组类型。请注意,SameLength<P> 只是一个长度与P 相同的元组,但其元素类型为any。这避免了P 被推断为非常窄的问题(比如A 是[number, number, number],然后P 是[0, 0]。您不能将[number, number, string] 拆分为[0, 0, ...infer S],因为number 不是'不能分配给0。但我们只关心这里的长度,所以我们将[number, number, string]拆分为[any, any, ...infer S],这样就可以了,并将S推断为string。
好的,使用并实现它:
function curry<A extends any[], R>(fn: (...args: A) => R): Curried<A, R> {
return (...args: any[]): any =>
args.length >= fn.length ? fn(...args as any) : curry((fn as any).bind(undefined, ...args));
}
我在curry() 的实现中使用了很多type assertions,因为编译器几乎绝望地试图验证返回的函数是否可以分配给Curried<A, R>。与其担心它,我只是告诉编译器不要费心验证安全性,并自己负责使实现正确。 (如果错了,我怪我自己,而不是编译器)。
好的,我们开始吧。有用吗?
const fn = (a: string, b: number, c: boolean) => (a.length <= b) === c ? "yep" : "nope";
const cFn = curry(fn);
const val1 = cFn("")(1)(true);
console.log(val1); // yep
const val2 = cFn("", 1, true);
console.log(val2); // yep
const val3 = cFn()()()()("", 1)()()(true); // yep
我觉得不错。请注意,根据我的定义,如果您调用不带参数的Curried<A, R>,您只会得到Curried<A, R>。以下是一些故意的错误,因此您可以看到编译器捕获了它们:
// errors
cFn(1, 1, true); // error!
// ~ <-- not a string
cFn("", 1, true, false); // error!
// ~~~~~ <-- Expected 0-3 arguments, but got 4
cFn("")(1)(false)(true); // error!
//~~~~~~~~~~~~~~~ <-- This expression is not callable.
这些在我看来是正确的错误。
Playground link to code