打字稿中的通用 zip,使用 variadc 元组

Generic zip in typescript, using variadc tuple

我有 python 背景,我喜欢使用函数式语言,而且我正在学习打字稿,所以我想要一个 zip() 函数,它的工作方式有点像 Python的。我看到打字稿已经获得了生成器和可变泛型,所以我认为这应该是可能的。我有一些东西 非常 接近我想要的东西,但还不完全是。我确定我要么在做一些显而易见的事情,要么我即将学习 typescript 类型系统的深层工作原理。

我想要的是一个函数,它接受可变数量的数组,并从这些数组中生成一个元素元组,直到最短的数组用完为止。我写了一个生成器来执行此操作,但我无法正确输入。

  function* zip<T extends any[]>(...args: T) {
    for (let i = 0; i < Math.min(...args.map((e) => { return e.length })); ++i) {
      yield args.map((e) => { return e[i]; }) as T;
    }
  }

如果我删除 as T 那么该函数可以工作,但我会丢失所有类型信息,如果没有,我会得到 A return 类型 [...T[]],但我想要 [...T].

例如zip([1, 2, 3], ['a', 'b', 'c']),应该有return类型的[number, string],但它有[number[], string[]]

您可能希望打字看起来像这样:

function* zip<T extends any[][]>(...args: T) {
    for (let i = 0; i < Math.min(...args.map((e) => { return e.length })); ++i) {
        yield args.map((e) => { return e[i]; }) as
            { [I in keyof T]: T[I] extends Array<infer E> ? E : never };
    }
}

请注意,您希望 args 成为 个数组 的数组,对吗? args 的每个元素本身都应该有一个 length 属性 并且具有可通过数字索引访问的元素。所以 T extends any[][] 会为你做这件事(你甚至可能想放宽它以便它也接受 readonly arrays,但我不会深入讨论。)

您不想 return T 生成器生成的每个元素,因为这是一个数组数组。如果你不注释 return 那么你会得到编译器认为 map() 产生的任何东西,这必然会比你正在寻找的更不精确(参见 for more information). You have to assert 类型, 但它不是 T.

相反,对于 T 的索引中的每个数字索引 I,您希望在该索引处采用数组类型 T[I] 并且 return 该数组的元素类型。我们可以使用 mapped type to represent this transformation, because mapped types on tuples produce tuples。这就是 { [I in keyof T]: T[I] extends Array<infer E> ? E : never } 所做的。

好的,我们来测试一下:

for (const z of zip([1, 2, 3], ['a', 'b', 'c'])) {
    console.log(z[0].toFixed(2) + ", " + z[1].toUpperCase())
    // "1.00, A" "2.00, B" "3.00, C"
}

看起来不错;编译器认为 z[number, string] 并让您相应地处理 z 的每个元素。

Playground link to code