Created
June 28, 2018 03:16
-
-
Save joedski/5607d1310971e2d0ce8ad3747cb5805b to your computer and use it in GitHub Desktop.
Wherein I finally learn how to unbox and rebox things in Typescript for great genericization justice and the great annoyance of my coworkers.
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
interface BoxA<T> { | |
a: T; | |
} | |
interface BoxB<T> { | |
b: T; | |
} | |
type BoxerA<T> = (a: T) => BoxA<T>; | |
interface ThingGetters { | |
[key: string]: <R>() => R; | |
} | |
const gs = { | |
foo: () => 'foo', | |
bar: () => 2, | |
baz: () => 14, | |
}; | |
type KeyofGS = keyof typeof gs; | |
type FnsofGS = (typeof gs)[KeyofGS]; | |
type RetsofGS = ReturnType<FnsofGS>; | |
type WGS = { | |
[K in keyof typeof gs]: () => BoxA<ReturnType<(typeof gs)[K]>> | |
}; | |
// prewrapped | |
function boxA<T>(a: T): BoxA<T> { | |
return { a }; | |
} | |
function boxB<T>(b: T): BoxB<T> { | |
return { b }; | |
} | |
const pwgs = { | |
foo: () => boxA('foo'), | |
bar: () => boxA(3), | |
}; | |
// The magic. (actually just the same as ReturnType.) | |
type UnboxA<B extends BoxA<any>> = | |
B extends BoxA<infer R> ? R : never; | |
// Proof of concept: Unbox a BoxA<T> (T = number, here) | |
type BoxANum = BoxA<number>; | |
type Num = UnboxA<BoxANum>; | |
// :: { foo: () => BoxA<string>; bar: () => BoxA<number> } | |
type PWGS = typeof pwgs; | |
// :: "foo" | "bar" | |
type PWGS_K = keyof PWGS; | |
// :: (() => BoxA<string>) | (() => BoxA<number>) | |
type PWGS_Gs = PWGS[PWGS_K]; | |
// :: BoxA<string> | BoxA<number> | |
type PWGS_Gs_Rt = ReturnType<PWGS_Gs>; | |
// :: BoxB<string | number> which is the same I guess? | |
// We don't have a specific key (K in keyof T) to bind to, | |
// so we get the type covering every case. | |
type PWGS_Gs_Rw = BoxB<UnboxA<ReturnType<PWGS_Gs>>>; | |
// :: BoxA<string> | |
type tfr = ReturnType<typeof pwgs.foo>; | |
// :: string | |
type tfrub = UnboxA<tfr>; | |
// :: () => BoxA<string> | |
// This works before the const statement. Hm. | |
type trwfr = ReturnType<typeof tmpwgs.foo>; | |
type ThunkMakifiedPWGS<PWGS extends typeof pwgs> = { | |
[K in keyof PWGS]: () => PWGS[K]; | |
} | |
const tmpwgs: ThunkMakifiedPWGS<typeof pwgs> = { | |
foo: () => pwgs.foo, | |
bar: () => pwgs.bar, | |
}; | |
// :: <T extends PWGS>{ [K in keyof T]: () => BoxB<UnboxA<ReturnType<T[K]>>> } | |
type RewrappedPWGS<PWGS extends typeof pwgs> = { | |
[K in keyof PWGS]: () => ( | |
// This is mildly annoying because we're basically repeating most of ReturnType<T>. | |
PWGS[K] extends (...args: any[]) => any | |
? BoxB<UnboxA<ReturnType<PWGS[K]>>> | |
: never | |
); | |
} | |
// Seems to check out. | |
const rewrappedPwgs: RewrappedPWGS<typeof pwgs> = { | |
// :: () => BoxB<string> | |
foo: () => boxB(pwgs.foo().a), | |
// :: () => BoxB<number> | |
bar: () => boxB(pwgs.bar().a), | |
} | |
// Finally, the genericized form. | |
type RewrappedGetters<T> = { | |
[K in keyof T]: ( | |
// This is mildly annoying because | |
// we're basically repeating most of ReturnType<T>. | |
T[K] extends (...args: any[]) => any | |
? (() => BoxB<UnboxA<ReturnType<T[K]>>>) | |
: never | |
) | |
} | |
// Use of that type. | |
function rewrappifyGetters<T>(gs: T): RewrappedGetters<T> { | |
const rwgs = {} as RewrappedGetters<T>; | |
Object.keys(gs).forEach((key) => { | |
rwgs[key] = () => boxB(gs[key]()); | |
}); | |
return rwgs; | |
} | |
// Example base item. (could have just used pwgs above, but eh.) | |
const wrappedGetters = { | |
foo: () => boxA('foo'), | |
bar: () => boxA(5), | |
}; | |
// Rewrapped. | |
const rewrappedGetters = rewrappifyGetters(wrappedGetters); | |
// :: () => BoxA<string> | |
wrappedGetters.foo; | |
// :: () => BoxB<string> awww yiss | |
rewrappedGetters.foo; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment