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?
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.