Skip to content

Instantly share code, notes, and snippets.

@blacktaxi
Last active March 28, 2018 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blacktaxi/da291cc067009172894c8cbb41536365 to your computer and use it in GitHub Desktop.
Save blacktaxi/da291cc067009172894c8cbb41536365 to your computer and use it in GitHub Desktop.
polymorphic lens in TypeScript 2.8
/**
* Gives you an object type compatible with given key type TKey.
*
* For example,
*
* type Example = ObjFor<'a'>
*
* is expanded to
*
* type Example = {
* a: any;
* }
*/
type ObjFor<K, T = any> = {
[P in keyof (K extends keyof infer O ? O : never)]: T
}
type Len<T> = T extends any[] ? T['length'] : never
/**
* Returns an object type compatible with a given nested key sequence KS.
*
* For example,
*
* type ExampleSeq = ObjForSeq<['a', 'b', 'c']>
*
* is expanded to
*
* type ExmapleSeq = {
* a: {
* b: {
* c: any;
* };
* };
* }
*/
// @TODO is there a way to do a cons on an array at the type level?
type ObjForSeq<KS extends string[]> = Len<KS> extends 4
? ObjFor<KS[0], ObjFor<KS[1], ObjFor<KS[2], ObjFor<KS[3]>>>>
: Len<KS> extends 3
? ObjFor<KS[0], ObjFor<KS[1], ObjFor<KS[2]>>>
: Len<KS> extends 2
? ObjFor<KS[0], ObjFor<KS[1]>>
: Len<KS> extends 1
? ObjFor<KS[0]>
: never
type PropType<O, K extends keyof O> = O[K]
/**
* Creates a polymorphic lens that looks at property k of an object.
*
* The type of object and the type of property is determined at use site.
* If the object doesn't have a property K, the lens is incompatible and
* the usage will not compile.
*/
function key<K extends string>(k: K) {
return {
get: <O extends ObjFor<K, PropType<O, K>>>(o: O): PropType<O, K> =>
o[k],
set: <O extends ObjFor<K, PropType<O, K>>>(o: O, v: PropType<O, K>): O =>
Object.assign({}, o, { [k]: v })
}
}
const example = {
a: 5,
b: '6',
c: { d: false }
}
const a_ = key('a')
const _1: number = a_.get(example)
// Argument of type '{ hello: number; }' is not assignable to parameter of type '{ a: any; }'.
// Object literal may only specify known properties, and 'hello' does not exist in type '{ a: any; }'.
const _2 = a_.get({ hello: 5 })
const _3: string = a_.get({ a: 'a string' })
const _4: { a: string } = a_.set({ a: 'hello' }, 'hallo')
// Argument of type '"hello"' is not assignable to parameter of type 'number'.
const _5 = a_.set(example, 'hello')
const _6: typeof example = a_.set(example, 6)
const _7: boolean = key('d').get(key('c').get(example))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment