我尝试添加一个名为'compose'的函数的类型,如下所示:
type First<T> = T extends [infer U, ...any[]] ? U : never
type Last<T> = T extends [...any[], infer U] ? U : never
// compose :: (f, g, ...) -> (a -> f(g(...(a))) )
function compose<Arr extends any[], Begin extends any>(
...fns: {
[I in keyof Arr]: I extends 0
? (arg: Begin) => First<Arr>
: (arg: [any, ...Arr][I] /**wrong: Validation type 'I' cannot be used for index type '[any,... Arr]' */) => Arr[I]
}
): (arg: Begin) => Last<Arr> {
return function (val) {
return fns.reduce((input, fn) => {
return fn(input)
}, val) as any
}
}
如上所述,打印脚本显示:验证类型“I”不能用于索引类型“[any,... Arr]”,
当我使用一些例子来测试这个函数时,另一个类型问题发生了:
function a(p: string): boolean {
return true
}
function b(p: boolean): number {
return 1
}
function c(p: number): Array<string> {
return []
}
const abc = compose(a, b, c)
abc
函数的参数未知,我无法解决这两个问题,所以我请求帮助,Playground:Playground
解决上述两个问题
1条答案
按热度按时间o75abkj41#
可惜的是,没有一种方便的方法来定义这种通用的变元复合函数。我第一次研究它时,我想到了the code in this answer,尽管已经过去了几年,但似乎没有什么比它更好的了。现在看来,我们只能勉强应付了。
为了让这个问题的代码工作,我需要做两个更改;一个次要的是抑制错误,一个主要的是使推理工作。次要的变化是替换
与
何处
也许在一个完美的世界里,编译器会明白,如果一个类似数字的泛型索引
I
是泛型数组类型Arr
的键,那么它也将是数组类型[any, ...Arr]
的键。* 我们 * 知道这是真的,因为我们有能力在泛型元组和索引上进行抽象推理,但是编译器并不知道这一点,必须有人显式地编程来检查这样的事情,这将花费额外的工作来编程,然后使编译器为这种情况做额外的工作检查,无论哪种情况,都不值得做额外的工作。在这种情况下,编译器不能理解
K
是T
的键,但我们需要得到T[K]
,我们可以使用Idx<T, K>
实用程序类型,它可以工作,因为有一个显式检查K extends keyof T
。最大的变化是取代
与
同样,编译器不具备与我们一样的抽象推理能力;不幸的是,它不能从
{ [I in keyof Arr]: I extends O ? (arg: Begin) => ⋯ : ⋯ }
类型的值推断出Begin
。有一些支持从同态mapped types推断(有关术语,请参阅What does "homomorphic mapped type" mean?),但这只允许您推断出被Map的对象。因此,您可能可以从{ [I in keyof Arr]: I extends O ? (arg: Begin) => ⋯ : ⋯ }
推断出Arr
,但不能从Begin
推断出Arr
。通过将该Map类型与
[(arg: Begin) => any, ...any]
交叉,我添加了另一个推断站点,编译器可以从该站点推断Begin
为“传递给函数的第一个参数的参数类型”。让我们试试看:
编译器推断
abc
的类型为(arg: string) => string[]
,这意味着Begin
被正确推断为string
。Playground链接到代码