Skip to content

Instantly share code, notes, and snippets.

@mitaki28
Created November 5, 2016 17:51
Show Gist options
  • Save mitaki28/ad39a69ab4fa73c99a822c0c3abc99dd to your computer and use it in GitHub Desktop.
Save mitaki28/ad39a69ab4fa73c99a822c0c3abc99dd to your computer and use it in GitHub Desktop.
Type-safe Lens (TypeScript 2.1)
// 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