Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Created April 10, 2015 16:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbuckton/ca48d72f15da485e69e9 to your computer and use it in GitHub Desktop.
Save rbuckton/ca48d72f15da485e69e9 to your computer and use it in GitHub Desktop.
Proposal: Generic "rest" type

Proposal

Add a generic "rest" type parameter, for use with Tuples. The goal of this feature is to provide better type support for certain operations that either cannot be reliably typed today, or could be more easily written.

Syntax

A generic "rest" type parameter is written in the form ...T. When generic types are inferred, this would be expanded into T0, T1, ... Tn. This type parameter can only be used inside of a tuple type, using the form [...T], which would be expanded into [T0, T1, ... Tn].

Scenarios

bind

Currently, if you wanted to write something like bind with generics, you have two options:

// (1) non-generic
function bind(func: (...args: any[]) => any, thisArg?: any): (...args: any[]) => any;

// (2) generic
function bind<T>(func: () => T, thisArg?: any): () => T;
function bind<A, T>(func: (a: A) => T, thisArg?: any): (a: A) => T;
function bind<A, B, T>(func: (a: A, b: B) => T, thisArg?: any): (a: A, b: B) => T;
function bind<A, B, C, T>(func: (a: A, b: B, c: C) => T, thisArg?: any): (a: A, b: B, c: C) => T;
// ...

Option 1 provides little to no type safety. Option 2 provides better type safety, but must be constantly maintained as new uses are introduced that need more overloads to support more arguments.

With a generic "rest" type, you could instead write:

function bind<...T, U>(func: (...args: [...T]) => U, thisArg?: any): (...args: [...T]) => U;

function a(): boolean;
function b(x: number): boolean;
function c(x: number, y: string): boolean;

let A = bind(a, {}); // () => boolean;
let B = bind(b, {}); // (_0: number) => boolean;
let C = bind(c, {}); // (_0: number, _1: string) => boolean;

Promise.all

The Promise.all helper suffers a similar issue, where you lose type safety:

class Promise<T> {
  all<T>(promises: (T | IPromise<T>)[]): Promise<T[]>;
}

let a: Promise<number>;
let b: Promise<string>;
let c: Promise<MyClass>;

// The type argument for type parameter 'T' cannot be inferred from the usage...
Promise.all([a, b, c]).then(([a, b, c]) => {
});

With a generic "rest" parameter, the following would possible:

declare class Promise<T> {
  public static all<...T>(promises: [...(T | IPromise<T>)]): Promise<[...T]>;
}

let a: Promise<number>;
let b: Promise<string>;
let c: Promise<MyClass>;

Promise.all([a, b, c]).then(([a, b, c]) => {
  a; // number
  b; // string
  c; // MyClass
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment