Skip to content

Instantly share code, notes, and snippets.

@bpander
Created July 2, 2019 01:32
Show Gist options
  • Save bpander/8641449810c9cdb6547230bb770f0f48 to your computer and use it in GitHub Desktop.
Save bpander/8641449810c9cdb6547230bb770f0f48 to your computer and use it in GitHub Desktop.
import { get, set } from 'lodash';
type InnerA = { type: 'num'; amount: number; }
type InnerB = { type: 'str'; name: string; }
type Inner = InnerA | InnerB;
interface Outer {
inners: Inner[];
}
type Setter<T> = (state: T) => T;
interface Lens<TInput, TOutput = TInput> {
k: <K extends keyof TOutput>(key: K) => Lens<TInput, TOutput[K]>;
set: (setter: Setter<TOutput>) => Setter<TInput>;
get: (outer: TInput) => TOutput;
compose: <TNewOutput>(childLens: Lens<TOutput, TNewOutput>) => Lens<TInput, TNewOutput>;
path: (string | number)[];
}
const makeLensFromPath = <TInput extends object, TOutput>(path: (string | number)[]): Lens<TInput, TOutput> => {
const k: Lens<TInput, TOutput>['k'] = key => makeLensFromPath([ ...path, key as (string | number) ]);
const compose: Lens<TInput, TOutput>['compose'] = lens => makeLensFromPath([ ...path, ...lens.path ]);
return {
k,
path,
set: (setter: Setter<TOutput>) => (input: TInput) => set(input, path, setter(get(input, path))),
get: (input: TInput) => get(input, path),
compose,
};
};
const makeLens = <TInput extends object>(): Lens<TInput> => {
return makeLensFromPath([]);
};
const outerLens = makeLens<Outer>();
const innerLens = makeLens<InnerB>();
const composed = outerLens.k('inners').k(0).compose(innerLens.k('name') as Lens<Inner, string>);
const val = composed.set(s => s + '!')({ inners: [ { type: 'str', name: 'hi' } ] });
console.log({ val });
const test2 = outerLens.get({ inners: [ { type: 'str', name: 'hi' } ] });
console.log(test2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment