Skip to content

Mapped tuple types #25947

Closed
Closed
@jscheiny

Description

@jscheiny

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions