Skip to content

Instantly share code, notes, and snippets.

@eps1lon
Last active June 19, 2018 15:51
Show Gist options
  • Save eps1lon/0a7288d4e5669dbbbe79f1b6624690a7 to your computer and use it in GitHub Desktop.
Save eps1lon/0a7288d4e5669dbbbe79f1b6624690a7 to your computer and use it in GitHub Desktop.
Tuple type inference in TypeScript
// The problem, playground: https://bit.ly/2t9gDij
const getFirst = (arg: [number, number]) => arg[0];
const getSecond = (arg: [number, number]) => arg[1];
const foo = [1, 2];
const foo_tuple = foo as Tuple2b<typeof foo>;
const firstBad = getFirst(foo); // $ExpectError
// the "solution"
/**
* first iteration with ts 2.8 conditional types. Turs out
* you don't need those.
*/
type Tuple2<T> = T extends Array<infer U> ? [U, U] : never;
/**
* casts arrays to tuples
* remember that there is no difference between T[0] and T[n]
* for arrays
*/
type Tuple2b<T extends Array<any>> = [T[0], T[0]];
/**
* returns an actual tuple (no more or less elements)
* creates a new array
* costs new array call and array.prototype.length call
*/
const asTupleActual = <T>(list: T[]): [T, T] => {
if (list.length !== 2) throw new Error('not a tuple');
return [list[0], list[1]];
}
/**
* identity function with runtime check
* costs array.prototype.length call
*/
const asTupleSafe = <T>(list: T[]): [T, T] => {
if (list.length !== 2) throw new Error('not a tuple');
return list as [T, T];
}
/**
* just a typecast, can return 1 or fewer elements
*/
const asTupleUnsafe = <T>(list: T[]): [T, T] => {
return list as [T, T];
}
const firstGoodA = getFirst(foo as Tuple2b<typeof foo>);
const firstGoodB = getFirst(foo_tuple);
const firstGoodC = getFirst(asTupleActual(foo));
const firstGoodD = getFirst(asTupleSafe(foo));
// can be undefined at runtime
const firstGoodE = getFirst(asTupleUnsafe(foo));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment