Skip to content

Instantly share code, notes, and snippets.

@Willmo36
Last active July 3, 2020 15:35
Show Gist options
  • Save Willmo36/47bdb7b5f58b680f874b05afd2df645f to your computer and use it in GitHub Desktop.
Save Willmo36/47bdb7b5f58b680f874b05afd2df645f to your computer and use it in GitHub Desktop.
fp-ts comonad builder
import { Comonad2C } from 'fp-ts/lib/Comonad';
import { FunctionN } from 'fp-ts/lib/function';
import { Monoid } from 'fp-ts/lib/Monoid';
declare module 'fp-ts/lib/HKT' {
interface URItoKind2<E, A> {
Builder: Builder<E, A>;
}
}
export const URI = 'Builder';
export type URI = typeof URI;
export interface Builder<A, B> {
_tag: URI;
(opts: A): B;
}
export const getComonadBuilder = <E>(M: Monoid<E>): Comonad2C<URI, E> => ({
URI,
_E: M.empty,
map: (fa, f) => mkBuilder(pa => f(fa(pa))),
extract: builder => builder(M.empty),
extend: (builder, setter) =>
mkBuilder(opts2 =>
setter(mkBuilder(opts1 => builder(M.concat(opts1, opts2)))),
),
});
export const mkBuilder = <E, B>(fn: FunctionN<[E], B>): Builder<E, B> => {
let b = fn as Builder<E, B>;
b._tag = URI;
return b;
};
type MyState = {
store1: {foo: number},
store2: {bar: number}
};
const initialState: MyState = {
store1: {foo: 0},
store2: {bar: 0},
}
//A store of stores
//allow for partial updating of a partial set of stores
//aka
type DoublePartial<T> = Partial<{[K in keyof T]: Partial<T[K]>}>;
export type StateBuilder = Builder<DoublePartial<MyState>, MyState>;
export type StateBuilderStep = (builder: StateBuilder) => MyState;
const monoidMyState: Monoid<DoublePartial<MyState>> = {
empty: initialState,
concat: (a, b) => {
return {
store1: {...a.store1, ...b.store1},
store2: {...a.store2, ...b.store2},
};
},
};
//We can use the Partial.concat here to save duplication
//but at the cost of discarding
const fromDoublePartial = (partial: DoublePartial<MyState>): MyState =>
// cast here to reuse the code above (sligtly cheating but saves LoC)
monoidMyState.concat(initialState, partial) as MyState;
export const comonadMyState = getComonadBuilder(monoidMyState);
export const buildFromSteps = (steps: StateBuilderStep[]) => {
const combinedBuilder = steps
.reverse()
.reduce(comonadMyState.extend, baseBuilder);
return comonadMyState.extract(combinedBuilder);
};
/**
* Given a final config (DoublePartial<MyState>)
* turn it into a MyState
*/
const baseBuilder: StateBuilder = makeBuilder(fromDoublePartial);
/**
* BUILDERS
*/
export const withRandomFoo: StateBuilderStep = builder =>
builder({store1: {foo: Math.random()}});
export const withRandomBar: StateBuilderStep = builder =>
builder({store2: {bar: Math.random()}});
export const state = buildFromSteps([
withRandomBar,
withRandomFoo
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment