Last active
September 17, 2019 19:01
-
-
Save HerringtonDarkholme/9ee7d7954cc088c6560eeda7b384dffc to your computer and use it in GitHub Desktop.
A demonstration of variadic generic in TS3.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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