Skip to content

Instantly share code, notes, and snippets.

@HerringtonDarkholme
Last active September 17, 2019 19:01
Show Gist options
  • Save HerringtonDarkholme/9ee7d7954cc088c6560eeda7b384dffc to your computer and use it in GitHub Desktop.
Save HerringtonDarkholme/9ee7d7954cc088c6560eeda7b384dffc to your computer and use it in GitHub Desktop.
A demonstration of variadic generic in TS3.0
/**
* This gist demonstrates how powerful TypeScript's new tuple type is.
* Say, we want to write a function that receive multiple arguments, and return a tuple of arguments' types.
* For example, `getTypes(1, true, 'str')` return a tuple `['number', 'boolean', 'string']`.
* We can do that in **type safe** way in TypeScript now! And actually we can do the computation at compile time!
* This is so like dependent type in TypeScript!
* Reference: https://github.com/Microsoft/TypeScript/pull/24897
*/
// Stack programming basic:
// Given a tuple type, return the type of its head or the tuple of its tails
type Head<T> = T extends [infer H, ...any[]] ? H : never;
// we cannot use [any, ...infer T] in TS yet, so use a conditoinal type inference instead
type Tail<L extends any[]> = ((...x: L) => void) extends ((h: any, ...rest: infer T)=>void) ? T : never
// @ts-ignore
type Fn<R extends any[]> = (...args: R) => void
// @ts-ignore
type Fn1<H, T extends any[]> = (h: H, ...args: T) => void
// merge one type and one tuple type to one tuple
// it is equivalent to [H, ...T], sadly no TS support now
// we use two functions and conditional type inference to mock it
type Merge<H, T extends any[]> = Fn1<H, T> extends Fn<infer R> ? R : []
// Reverse a tuple type, e.g [1, 2, 3] => [3, 2, 1]
// use lookup type to work around cyclic reference
// we also need another type parameter X to accumulate result types
// so that `Reverse`'s bound isn't used during recursion (to avoids type cycle)
// this technique is like tail recursion at type level
// http://wiki.c2.com/?TailRecursion
type Reverse<L extends any[], X extends any[] = []> = {
1: X,
// @ts-ignore
0: Reverse<Tail<L>, Merge<Head<L>, X>>
}[L extends [] ? 1 : 0]
// the heterogeneous mapping type
// given a type, return a string representation
type GetType<T> =
T extends number ? 'number' :
T extends string ? 'string' :
T extends boolean ? 'boolean' : 'other'
// apply `GetType` to a tuple whose elements have different types!
// the technique is similar to `Reverse`. Does it remind you of `fold` in SICP?
// @ts-ignore
type GetTypes<L extends any[]> = Reverse<GetTypesAux<L>>
type GetTypesAux<L extends any[], X extends any[] = []> = {
1: X,
// @ts-ignore
0: GetTypesAux<Tail<L>, Merge<GetType<Head<L>>, X>>
}[L extends [] ? 1 : 0]
type H = GetTypes<[1, true, 'str']> // ['number', 'boolean', 'string']
// use it in function like dependent type ;)
declare function getTypes<T extends any[]>(...args: T): GetTypes<T>
var a = getTypes(1, true, 'str')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment