typescript 无法添加名为“compose”的函数的类型

k2fxgqgv  于 2023-04-07  发布在  TypeScript
关注(0)|答案(1)|浏览(105)

我尝试添加一个名为'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
解决上述两个问题

o75abkj4

o75abkj41#

可惜的是,没有一种方便的方法来定义这种通用的变元复合函数。我第一次研究它时,我想到了the code in this answer,尽管已经过去了几年,但似乎没有什么比它更好的了。现在看来,我们只能勉强应付了。
为了让这个问题的代码工作,我需要做两个更改;一个次要的是抑制错误,一个主要的是使推理工作。次要的变化是替换

[any, ...Arr][I]

Idx<[any, ...Arr], I>

何处

type Idx<T, K> = K extends keyof T ? T[K] : never;

也许在一个完美的世界里,编译器会明白,如果一个类似数字的泛型索引I是泛型数组类型Arr的键,那么它也将是数组类型[any, ...Arr]的键。* 我们 * 知道这是真的,因为我们有能力在泛型元组和索引上进行抽象推理,但是编译器并不知道这一点,必须有人显式地编程来检查这样的事情,这将花费额外的工作来编程,然后使编译器为这种情况做额外的工作检查,无论哪种情况,都不值得做额外的工作。
在这种情况下,编译器不能理解KT的键,但我们需要得到T[K],我们可以使用Idx<T, K>实用程序类型,它可以工作,因为有一个显式检查K extends keyof T
最大的变化是取代

...fns: { [I in keyof Arr]: I extends 0
  ? (arg: Begin) => First<Arr> : ⋯ }

...fns: [(arg: Begin) => any, ...any] & 
  { [I in keyof Arr]: I extends 0
  ? (arg: Begin) => First<Arr> : ⋯ }

同样,编译器不具备与我们一样的抽象推理能力;不幸的是,它不能从{ [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为“传递给函数的第一个参数的参数类型”。
让我们试试看:

function a(p: string): boolean { return p === p.toUpperCase() }
function b(p: boolean): number { return p ? 1 : 0 }
function c(p: number): Array<string> { 
  return Array.from({ length: p }, _ => "" + p) 
}

const abc = compose(a, b, c);
// const abc: (arg: string) => string[]

console.log(abc("YES")); // ["1"]
console.log(abc("no")); // []

编译器推断abc的类型为(arg: string) => string[],这意味着Begin被正确推断为string
Playground链接到代码

相关问题