Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to infer the array element type of each rest parameter, and create a tuple type from it?

I have taken a zip generator function, and I’m trying to infer the result type. Given zip(A, B) with A Iterable<string> and B Iterable<number> I’d want to infer the return type Generator<[string, number][]>

Here’s what I got so far:

declare function zip<T extends Iterable<any>[]>(...iterables: T): Generator<T>:

The problem is that it infers a type union Generator<[string[], number[]]> instead.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The closest I think I got is something like this:

declare function zip<
  T extends Iterable<any>[],
  K = T extends Iterable<infer K>[] ? K : never,
>(...iterables: T): Generator<[...K[]]>

but that still just yields a Generator<(string | number)[]>, trying to infer it on the spot I just get Generator<any[]>:

declare function zip<T extends Iterable<any>[]>(
  ...iterables: T
): Generator<[...(T extends Iterable<infer K>[] ? K : never)]>

My desired output is:

declare const A: string[];
declare const B: Iterable<number>;
const z = zip(A, B);
//    ^? const z: Generator<[string, number][]>

>Solution :

I would be inclined to use the following call signature:

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

Here the generic type parameter T corresponds to the element type of the expected output, so in your zip(A, B) example, where A is Iterable<X> and B is Iterable<Y>, then T would be [X, Y].

Then the iterables rest parameter is of the mapped array/tuple type { [I in keyof T]: Iterable<T[I]> }. A mapped array/tuple turns arrays/tuples into arrays/tuples and it essentially iterates only over the numeric/numeric-like indexes of the array/tuple. Meaning that you can think of the I in keyof T as iterating over "0", "1", etc., up to the largest index of the tuple. Thus {[I in keyof T]: Iterable<T[I]>} will turn a tuple of types into a tuple of iterables of those types (e.g., [X, Y] becomes [Iterable<X>, Iterable<Y>]). Because it’s a homomorphic mapped type (see What does "homomorphic mapped type" mean?), TypeScript is able to infer T from { [I in keyof T]: Iterable<T[I]> }.

Let’s test it out:

declare const A: string[];
declare const B: Iterable<number>;
const z = zip(A, B);
//    ^? const z: Generator<[string, number][]>

Looks good. Given the rest input argument of type [string[], Iterable<number>], the compiler is able to infer T as [string, number], and then the output is of type Generator<[string, number][]>, as desired.

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading