Created
September 6, 2017 22:56
-
-
Save eddking/5d9cb476920d23e4a4539a906ee57d71 to your computer and use it in GitHub Desktop.
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
import { Dictionary, Entry, entries, dict } from './Dictionary'; | |
import { KVPair, kvPair } from './object'; | |
import { identity } from './utils'; | |
type Transformer<C, X> = (c: C, context?: X) => C; | |
type Getter<S, C> = (s: S) => C; | |
type Setter<S, C> = (s: S, v: C) => S; | |
type Reducer<R, C, X> = (sum: R, value: C, context: X) => R; | |
type Pair<A, B> = [A, B]; | |
export function lens<S>(): Lens<S, S> { | |
return new Lens(identity, identity) | |
} | |
// TODO: | |
// Create a lens that does not loose the context object when composed with another lens | |
// Then we can avoid making everything into a traversal | |
// we can do this by making a lens get it's own context via a function of the 'getter' | |
abstract class Traversable<S, C, X> { | |
public static compose<O, S, C, X, Y>( | |
a: Traversable<S, C, X>, | |
b: Traversable<O, S, Y> | |
): Traversal<O, C, X & Y> { | |
let newTraverse: TraverseFunc<O, C, X & Y> = (func) => { | |
return b.traverse((outer: S, why: Y) => { | |
return a.traverse((inner: C, ecs: X): C => { | |
let mergedContext: X & Y = { ...(why || {}), ...(ecs || {}) } as any; | |
return func(inner, mergedContext); | |
})(outer); | |
}); | |
}; | |
return new Traversal(newTraverse); | |
} | |
protected abstract traverse(func: Transformer<C, X>): Transformer<S, X>; | |
public modify(func: (val: C, context: X) => C): (s: S) => S { | |
return (s) => this.traverse(func)(s); | |
} | |
protected asListWithContext(s: S): Pair<C, X>[] { | |
let elements: Pair<C, X>[] = []; | |
this.traverse((c: C, x: X) => { | |
elements.push([c, x]); | |
return c; | |
})(s); | |
return elements; | |
} | |
public asList(): (s: S) => C[] { | |
return (s) => this.asListWithContext(s).map(([c, x]) => c); | |
} | |
public fold<R>(reducer: Reducer<R, C, X>, initial: R): (s: S) => R { | |
return (s) => { | |
return this.asListWithContext(s).reduce( | |
(sum, [value, context]) => reducer(sum, value, context), | |
initial | |
); | |
}; | |
} | |
public as<K extends string>(key: K): Traversal<S, C, X & KVPair<K, C>> { | |
return Traversable.compose(remember<C, K>(key), this); | |
} | |
} | |
class Lens<S, C> extends Traversable<S, C, {}> { | |
private getter: Getter<S, C>; | |
private setter: Setter<S, C>; | |
constructor( | |
getter: Getter<S, C>, | |
setter: Setter<S, C>, | |
) { | |
super(); | |
this.getter = getter; | |
this.setter = setter; | |
} | |
public then<O, Y>(other: Lens<C, O>): Lens<S, O>; | |
public then<O, Y>(other: Traversal<C, O, Y>): Traversal<S, O, Y>; | |
public then<O, Y>(other: Lens<C, O> | Traversal<C, O, Y>): Lens<S, O> | Traversal<S, O, Y> { | |
if (other instanceof Traversal) { | |
return Traversable.compose(other, this); | |
} | |
let newGetter: Getter<S, O> = (s) => { | |
return other.getter(this.getter(s)); | |
}; | |
let newSetter: Setter<S, O> = (s, v) => { | |
return this.setter(s, other.setter(this.getter(s), v)); | |
}; | |
return new Lens( | |
newGetter, | |
newSetter, | |
); | |
} | |
public compose<O, Y>(other: Lens<O, S>): Lens<O, C>; | |
public compose<O, Y>(other: Traversal<O, S, Y>): Traversal<O, C, Y>; | |
public compose<O, Y>(other: Lens<O, S> | Traversal<O, S, Y>): Lens<O, C> | Traversal<O, C, Y> { | |
if (other instanceof Traversal) { | |
return Traversable.compose(this, other); | |
} | |
return other.then(this); | |
} | |
public traverse(func: Transformer<C, {}>): Transformer<S, {}> { | |
return (s, x) => this.setter(s, func(this.getter(s), x || {})); | |
} | |
public set(value: C): (s: S) => S { | |
return (s) => this.setter(s, value); | |
} | |
public get(s: S): C { | |
return this.getter(s); | |
} | |
public toSelector(): (s: S) => C { | |
return this.getter; | |
} | |
} | |
type TraverseFunc<S, C, X> = (func: Transformer<C, X>) => Transformer<S, X>; | |
class Traversal<S, C, X> extends Traversable<S, C, X>{ | |
private traverseFunc: TraverseFunc<S, C, X>; | |
constructor(traverse: TraverseFunc<S, C, X>) { | |
super(); | |
this.traverseFunc = traverse; | |
} | |
public then<O, Y>(other: Traversable<C, O, Y>): Traversal<S, O, X & Y> { | |
return Traversable.compose(other, this); | |
} | |
public compose<O, Y>(other: Traversable<O, S, Y>): Traversal<O, C, X & Y> { | |
return Traversable.compose(this, other); | |
} | |
public traverse(func: Transformer<C, X>): Transformer<S, X> { | |
return this.traverseFunc(func); | |
} | |
public set(value: C): (s: S) => S { | |
return this.traverse(() => value); | |
} | |
} | |
class Isomorphism<S, C> extends Lens<S, C> { | |
private forward: (s: S) => C; | |
private backward: (c: C) => S; | |
constructor(forward: (s: S) => C, backward: (c: C) => S) { | |
super(forward, (s, c) => backward(c)); | |
this.forward = forward; | |
this.backward = backward; | |
} | |
public inverse(): Isomorphism<C, S> { | |
return new Isomorphism(this.backward, this.forward); | |
} | |
} | |
export function remember<S, K extends string>(key: K): Traversal<S, S, KVPair<K, S>> { | |
return new Traversal((func: Transformer<S, KVPair<K, S>>) => (val: S) => { | |
return func(val, kvPair(key, val)); | |
}); | |
} | |
export function getProperty<I, K extends keyof I>(key: K): Lens<I, I[K]> { | |
let getter = (i: I) => i[key]; | |
let setter = (i: I, val: I[K]) => ({ | |
...(i as any), | |
[key as any]: val, | |
}); | |
return new Lens(getter, setter); | |
} | |
export function optional<T>(): Traversal<T, T, {}> { | |
return new Traversal((func) => (val, context) => { | |
if (val === undefined) { | |
return undefined; | |
} | |
return func(val, context || {}); | |
}); | |
} | |
export function eachItem<I>(): Traversal<I[], I, {}> { | |
return new Traversal((func) => (array, context) => { | |
return array.map((val) => func(val, context || {})); | |
}); | |
} | |
export function eachEntry<T>(): Traversal<Dictionary<T>, Entry<T>, {}> { | |
return new Traversal((func) => (dictionary, context) => { | |
return dict(entries(dictionary).map((item) => func(item, context || {}))); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment