I’m writing a variadic zip function which takes any number of Iterable
s and returns a Generator
which yields tuples with items from each one, much like Python’s zip:
I started with the following function:
type IterablesTuple<T> = {
[K in keyof T]: T[K] extends Iterable<infer U> ? U | undefined : never
};
function* zip1<T extends Iterable<any>[]>(...args: T): Generator<IterablesTuple<T>> {}
Calling it with a few iterables:
const z1 = zip1([1, 2], ['a', 'b'], new Set([true, false]));
I see that the generator z1
returns tuples where each member has the same type as the value produced by the corresponding Iterable
‘s Iterator
:
const z1: Generator<[number | undefined, string | undefined, boolean | undefined], any, unknown>
However, I was then surprised to discover that the following also works:
type Iterables<T> = { [K in keyof T]: Iterable<T[K]> };
type ZipItem<T> = { [K in keyof T]: T[K] | undefined };
function* zip2<T extends any[]>(...args: Iterables<T>): Generator<ZipItem<T>> {}
Calling zip2
with the same iterables:
const z2 = zip2([1, 2], ['a', 'b'], new Set([true, false]));
I expected to see the following:
const z2: Generator<[number[] | undefined, string[] | undefined, Set<boolean> | undefined], any, unknown>
But for some reason, the generator z2
has the same type as z1
:
const z2: Generator<[number | undefined, string | undefined, boolean | undefined], any, unknown>
And yet, the following tests behave as I expected:
type ZipItemTest = ZipItem<[number[], string[], Set<boolean>]>;
// -> type ZipItemTest = [number[] | undefined, string[] | undefined, Set<boolean> | undefined]
type IterablesTupleTest = IterablesTuple<[number[], string[], Set<boolean>]>;
// -> type IterablesTupleTest = [number | undefined, string | undefined, boolean | undefined]
I did notice that this inconsistency goes away if the type of args
in zip2
is simply T
instead of Iterables<T>
.
How is ZipItem
able to accomplish the same transformation as IterablesTuple
in the context of a function, when Iterables<T>
is the type of args
, but not outside when it’s used on its own as seen in the two tests above?
Here’s a Playground where I have been experimenting with this.