typescripttypesgeneratorvariadic

Variadic zip over iterables in TypeScript


I'm trying to write a variadic zip over iterables while also preserving type. For example,

function* naturals(max=10) { for (let i=0; i<max; i++) yield i }
const x = [1, 2, 3]
const y = ["a", "b", "c"]

const zipped = zip(naturals(), x, y)
console.log([...zipped]) // => [[0, 1, "a"], [1, 2, "b"], [2, 3, "c"]]

function* zip<?>(...iterables:?[]) {
   const iterators = iterables.map(iter => iter[Symbol.iterator]())
   ...
}

Is there a type signature which helps preserve my types?


Solution

  • You could give zip() the following call signature:

    declare function zip<T extends any[]>(
      ...iterables: { [I in keyof T]: Iterable<T[I]> }
    ): Iterable<T>;
    

    It is generic in the type parameter T representing the type of the arrays in each element of the output (i.e., the function returns Iterable<T>). This is expected to be a tuple type.

    The input type is a rest parameter of a mapped type over the array/tuple type T, where each element of T is wrapped in Iterable. This mapped type is homomorphic (featuring in keyof T, see What does "homomorphic mapped type" mean? for more info) and so TypeScript can infer T from it.

    Let's try it on your example:

    const zipped = zip(naturals(), x, y)
    // const zipped: Iterable<[number, number, string]>
    

    Here iterables is of a tuple type assignable to [Iterable<number>, Iterable<number>, Iterable<string>], and so the compiler infers T as [number, number, string]. So the output type is Iterable<[number, number, string]> as expected.

    Playground link to code