Description
Search Terms
generic mapped tuple types overrides
Suggestion
The syntax here is up for debate, but the basic idea is to be able to apply the idea behind mapped object types to tuples. I would suggest the following:
type MappedTuple<T extends any[]> = [F<E> for E in T];
This would apply a type mapping to each of the element types of the tuple.
For example:
type PromiseTuple<T extends any[]> = [Promise<E> for E in T];
type T1 = Promise<[number, string, boolean]>;
// [Promise<number>, Promise<string>, Promise<boolean>]
Use Cases
This would make it much easier to write functions that take a variable number of arguments that all have to conform to some form and be able to reference the underlying type. This eliminates the need to write a ton of overrides:
all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
...
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
Examples
The basic usage of this would be as follows:
type TypeMap<T> = { wrap: T };
type MapTuple<T extends any[]> = [TypeMap<E> for E in T];
type T2 = MapTuple<[number, string, ...boolean[]]>
// [{ wrap: number }, { wrap: string }, ...{ wrap: boolean }[]];
There are many compelling examples of how this would simplify library code. For example, above, instead of having 10 overrides for Promise.all
, we could define it simply as:
type PromiseLikeTuple<T extends any[]> = [E | PromiseLike<E> for E in T];
all<T extends any[]>(...values: PromiseLikeTuple<T>): Promise<T>;
Similarly RxJS' combineLatest
would similarly benefit (Note: I've moved the project
argument to the head of the list because rest arguments must appear last, this differs from the current definition of combineLatest
)
type ObservableTuple<T extends any> = [ObservableInput<E> for E in T];
combineLatest<T extends any[], R>(project: (...values: T) => R, ...observables: ObservableTuple<T>): Observable<R>;
This is especially useful because there are only 6(?) overrides for combineLatest
meaning that providing 7 or more observable inputs would mean that your types would no longer be strict.
Currently
I don't believe that it is possible to construct a mapped tuple type currently. This would be made possible with the addition of recursive generic types and made easy to use with the addition of generic generic type arguments:
// Currently possible
type Head<T extends any[]> = T extends [infer U, ...any[]] ? U : never;
type Tail<T extends any[]> = ((...args: T) => void) extends (head: any, ...tail: infer U) => any ? U : never;
// Currently errors
type SpecificMapTuple<T extends any[]> = [F<Head<T>>, ...SpecificMapTuple<Tail<T>>];
type GenericMapTuple<F<~>, T extends any[]> =[F<Head<T>>, ...GenericMapTuple<F, Tail<T>>];
function all<T extends any[]>(...values: GenericMapTuple<PromiseLike, T>): Promise<T>;
Note: I think adding this functionality via recursive generic types and high order generic type arguments is probably a more flexible and elegant solution that will enable even more interesting typings but it would be nice to have both of those and the mapped tuple types above.
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript / JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. new expression-level syntax)