如何在 TypeScript 中输入 autocurry
How to type an autocurry in TypeScript
我正在尝试为以下函数编写类型:
const curry = (
f, arr = []
) => (...args) => (
a => a.length >= f.length ?
f(...a) :
curry(f, a)
)([...arr, ...args]);
我发现 this neat article 为 curry
创建了一个类型,如下所示:
type Head<T extends any[]> =
T extends [any, ...any[]]
? T[0]
: never;
type Tail<T extends any[]> =
((...t: T) => any) extends ((_: any, ...tail: infer TT) => any)
? TT
: [];
type HasTail<T extends any[]> =
T extends ([] | [any])
? false
: true;
type Curry<P extends any[], R> =
(arg: Head<P>) => HasTail<P> extends true
? Curry<Tail<P>, R>
: R;
我的问题是,此 Curry
类型的签名与我的 curry
函数的签名不匹配(或者我的 TypeScript 技能很差,但确实如此?)。此外,我无法弄清楚如何为 curry
编写与 Curry
类型匹配的实现。
如何为我的 curry
实现类型?使用 Curry
类型的实现看起来如何?
这里有一个主要警告:Function.length
很奇怪;它只会对没有默认参数或剩余参数的函数表现良好。例如,根据您的 TS 编译器针对的 JS 版本,您可能会针对以下代码得到不同的答案:
console.log(((arg = 1) => { }).length); // 0? 1?
因此,当您使用任何依赖于函数参数长度的运行时反射的东西时,请记住这一点。
您的 curry()
函数可分配给 Curry
类型定义,但反之则不行。如果底层函数仍然需要参数,则 Curry
类型定义每次调用时都只需要一个参数,而您的 curry
在调用时将接受任意数量的参数。这意味着我们可以给 curry()
类型 Curry
,但是编译器会限制你调用它的参数数量。它可能看起来像这样:
function curry<P extends any[], R>(f: (...args: P) => R): Curry<Required<P>, R> {
const _curry = (
f: (...args: any) => any, arr: any[] = []
) => (...args: any) => (
a => a.length >= f.length ?
f(...a) :
_curry(f, a)
)([...arr, ...args]);
return _curry(f);
}
在这里,我们基本上放弃了让编译器在实现内部进行任何实际类型检查;里面的一切都或多或少是 any
类型。您可能可以在那里获得更好的类型安全性,但不会太多;编译器将无法验证附加到元组类型末尾所涉及的操作,您最终会在任何地方使用 type assertions。
Required<P>
位可能不是必需的;这仅取决于您希望看到带有可选参数的函数发生什么。总的来说,我会非常小心地在联合类型的任何函数上使用它,或者它的参数列表可以有不同的长度等。
让我们确保编译器对正常使用感到满意:
function test(x: string, y: number, z: boolean) {
return z ? x : y;
}
const t0 = curry(test);
const t1 = t0("abc");
const t2 = t1(123);
const t3 = t2(true);
console.log(t3); // abc
const t4 = t2(false);
console.log(t4); // 123
看起来不错。
至于如何为 curry()
的一次可能多个参数的完整版本进行 TS 类型化,它需要类型级别的元组连接,目前不直接支持( see microsoft/TypeScript#5453). You can write something that works involving recursive conditional types, but since recursive types are also not directly supported (see microsoft/TypeScript#26980),我不会推荐它们用于生产系统。
或者你可以选择一些最大长度来支持,比如一次三个参数,然后编写一个适用于此的 Curry
版本,但我不知道这是否真的值得:
type Curry3<P extends any[], R> = P extends [] ? R : (
((a0: Head<P>) => Curry3<Tail<P>, R>) & (
P extends [any] ? unknown : (
((a0: Head<P>, a1: Head<Tail<P>>) => Curry3<Tail<Tail<P>>, R>) & (
P extends [any, any] ? unknown : (
((a0: Head<P>, a1: Head<Tail<P>>, a2: Head<Tail<Tail<P>>>) =>
Curry3<Tail<Tail<Tail<P>>>, R>)
)
)
)
)
);
特别是因为该特定定义使用了 overloads,它可能在任何地方都无法正常播放。
总之,希望对您有所帮助;祝你好运!
我正在尝试为以下函数编写类型:
const curry = (
f, arr = []
) => (...args) => (
a => a.length >= f.length ?
f(...a) :
curry(f, a)
)([...arr, ...args]);
我发现 this neat article 为 curry
创建了一个类型,如下所示:
type Head<T extends any[]> =
T extends [any, ...any[]]
? T[0]
: never;
type Tail<T extends any[]> =
((...t: T) => any) extends ((_: any, ...tail: infer TT) => any)
? TT
: [];
type HasTail<T extends any[]> =
T extends ([] | [any])
? false
: true;
type Curry<P extends any[], R> =
(arg: Head<P>) => HasTail<P> extends true
? Curry<Tail<P>, R>
: R;
我的问题是,此 Curry
类型的签名与我的 curry
函数的签名不匹配(或者我的 TypeScript 技能很差,但确实如此?)。此外,我无法弄清楚如何为 curry
编写与 Curry
类型匹配的实现。
如何为我的 curry
实现类型?使用 Curry
类型的实现看起来如何?
这里有一个主要警告:Function.length
很奇怪;它只会对没有默认参数或剩余参数的函数表现良好。例如,根据您的 TS 编译器针对的 JS 版本,您可能会针对以下代码得到不同的答案:
console.log(((arg = 1) => { }).length); // 0? 1?
因此,当您使用任何依赖于函数参数长度的运行时反射的东西时,请记住这一点。
您的 curry()
函数可分配给 Curry
类型定义,但反之则不行。如果底层函数仍然需要参数,则 Curry
类型定义每次调用时都只需要一个参数,而您的 curry
在调用时将接受任意数量的参数。这意味着我们可以给 curry()
类型 Curry
,但是编译器会限制你调用它的参数数量。它可能看起来像这样:
function curry<P extends any[], R>(f: (...args: P) => R): Curry<Required<P>, R> {
const _curry = (
f: (...args: any) => any, arr: any[] = []
) => (...args: any) => (
a => a.length >= f.length ?
f(...a) :
_curry(f, a)
)([...arr, ...args]);
return _curry(f);
}
在这里,我们基本上放弃了让编译器在实现内部进行任何实际类型检查;里面的一切都或多或少是 any
类型。您可能可以在那里获得更好的类型安全性,但不会太多;编译器将无法验证附加到元组类型末尾所涉及的操作,您最终会在任何地方使用 type assertions。
Required<P>
位可能不是必需的;这仅取决于您希望看到带有可选参数的函数发生什么。总的来说,我会非常小心地在联合类型的任何函数上使用它,或者它的参数列表可以有不同的长度等。
让我们确保编译器对正常使用感到满意:
function test(x: string, y: number, z: boolean) {
return z ? x : y;
}
const t0 = curry(test);
const t1 = t0("abc");
const t2 = t1(123);
const t3 = t2(true);
console.log(t3); // abc
const t4 = t2(false);
console.log(t4); // 123
看起来不错。
至于如何为 curry()
的一次可能多个参数的完整版本进行 TS 类型化,它需要类型级别的元组连接,目前不直接支持( see microsoft/TypeScript#5453). You can write something that works involving recursive conditional types, but since recursive types are also not directly supported (see microsoft/TypeScript#26980),我不会推荐它们用于生产系统。
或者你可以选择一些最大长度来支持,比如一次三个参数,然后编写一个适用于此的 Curry
版本,但我不知道这是否真的值得:
type Curry3<P extends any[], R> = P extends [] ? R : (
((a0: Head<P>) => Curry3<Tail<P>, R>) & (
P extends [any] ? unknown : (
((a0: Head<P>, a1: Head<Tail<P>>) => Curry3<Tail<Tail<P>>, R>) & (
P extends [any, any] ? unknown : (
((a0: Head<P>, a1: Head<Tail<P>>, a2: Head<Tail<Tail<P>>>) =>
Curry3<Tail<Tail<Tail<P>>>, R>)
)
)
)
)
);
特别是因为该特定定义使用了 overloads,它可能在任何地方都无法正常播放。
总之,希望对您有所帮助;祝你好运!