首页 > 解决方案 > 打字稿中的通用 zip,使用 variadc 元组

问题描述

我有 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 type [...T[]],但我想要[...T].

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

标签: typescriptgenericsvariadic-tuple-types

解决方案


您可能想要看起来像这样的类型:

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成为数组数组,对吗?should 的每个元素args本身都有一个length属性,并且可以通过数字索引访问元素。所以T extends any[][]会为你做到这一点(你甚至可能想放松它,以便它也接受readonly数组,但我不会进入那个。)

您不想T为生成器产生的每个元素返回,因为那是一个数组数组。如果您不注释返回,那么您将得到编译器认为map()产生的任何内容,这必然会比您要查找的内容更不精确(有关更多信息,请参阅将元组类型的值映射到不同的元组类型的值而不进行强制转换)。你必须断言类型,但它不是T

相反,对于的索引I中的每个数字索引T,您希望获取该T[I]索引处的数组类型并返回该数组的元素类型。我们可以使用映射类型来表示这种转换,因为元组上的映射类型会产生元组。就是{ [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 代码链接


推荐阅读