Created
November 5, 2016 17:51
-
-
Save mitaki28/ad39a69ab4fa73c99a822c0c3abc99dd to your computer and use it in GitHub Desktop.
Type-safe Lens (TypeScript 2.1)
This file contains 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
// requires TypeScript 2.1 or higher | |
export abstract class Lens<T, U> { | |
abstract get: (obj: T) => U; | |
abstract set: (value: U) => (obj: T) => T; | |
then = <V>(lens: Lens<U, V>) => new ComposedLens(this, lens); | |
thenKey = <L extends keyof U>(key: L): Lens<T, U[L]> => this.then(new ObjectLens<U, L>(key)); | |
modify = (f: (value: U) => U) => (obj: T) => this.set(f(this.get(obj)))(obj); | |
} | |
export class IdLens<T> extends Lens<T, T> { | |
get = (obj: T) => obj; | |
set = (value: T) => (obj: T) => value; | |
} | |
export class ComposedLens<T, U, V> extends Lens<T, V> { | |
constructor(private l1: Lens<T, U>, private l2: Lens<U, V>) { super(); } | |
get = (obj: T) => this.l2.get(this.l1.get(obj)); | |
set = (value: V) => (obj: T) => this.l1.set(this.l2.set(value)(this.l1.get(obj)))(obj); | |
} | |
export class ObjectLens<T, K extends keyof T> extends Lens<T, T[K]> { | |
constructor(private k: K) { super(); } | |
get = (obj: T) => obj[this.k]; | |
set = (value: T[K]) => (obj: T) => { | |
if (Array.isArray(obj)) { | |
const clone: T = obj.concat() as any; | |
clone[this.k] = value; | |
return clone; | |
} else { | |
const clone: T = Object.assign({}, obj); | |
clone[this.k] = value; | |
return clone; | |
} | |
}; | |
} | |
export const id = <T>() => new IdLens<T>(); | |
export const key = <T>() => <K extends keyof T>(key: K) => new ObjectLens<T, K>(key); | |
const exec = <T>(value: T) => (...fs: ((lens: Lens<T, T>) => (value: T) => T)[]): T => | |
fs.reduce((value, f) => f(id<T>())(value), value); | |
interface Address { | |
state: string; | |
city: string; | |
} | |
interface Person { | |
name: string; | |
address: Address; | |
}; | |
let x: Person[] = [{ | |
name: "Taro", | |
address: { | |
state: "Hoge-ken", | |
city: "Fuga-cho" | |
} | |
}]; | |
console.log("x: ", x); | |
const y = key<Person[]>()(0) | |
.then(key<Person>()("address")) | |
.then(key<Address>()("state")) | |
.set("Piyo-ken")(x); | |
console.log("x: ", x); | |
console.log("y: ", y); | |
const z = id<Person[]>() | |
.thenKey(0) | |
.thenKey("address") | |
.thenKey("city") | |
.modify((c) => c.toUpperCase())(x); | |
console.log("x: ", x); | |
console.log("z: ", z); | |
const w = exec(x)( | |
($) => $.thenKey(0).thenKey("name").set("Jiro"), | |
($) => $.thenKey(0).thenKey("address").thenKey("state").set("Piyo-ken"), | |
($) => $.thenKey(0).thenKey("address").thenKey("city").modify((c) => c.toLowerCase()), | |
); | |
console.log("x: ", x); | |
console.log("w: ", w); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment