Skip to content

Instantly share code, notes, and snippets.

@eddking
Created September 6, 2017 22:56
Show Gist options
  • Save eddking/5d9cb476920d23e4a4539a906ee57d71 to your computer and use it in GitHub Desktop.
Save eddking/5d9cb476920d23e4a4539a906ee57d71 to your computer and use it in GitHub Desktop.
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