Created
December 14, 2020 18:24
-
-
Save Willmo36/bfc5d0f5ed1b56141a2001cfed9e3399 to your computer and use it in GitHub Desktop.
fp-ts comonad with example
This file contains 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 { Comonad2C } from 'fp-ts/lib/Comonad'; | |
import { pipe } from 'fp-ts/lib/function'; | |
import { FunctionN } from 'fp-ts/lib/function'; | |
import { Monoid } from 'fp-ts/lib/Monoid'; | |
import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'; | |
import { pipeable } from 'fp-ts/lib/pipeable'; | |
/** | |
* OOP style builder pattern but in FP | |
* References: | |
* http://www.haskellforall.com/2013/02/you-could-have-invented-comonads.html | |
* https://kodimensional.dev/posts/2019-03-25-comonadic-builders | |
* https://github.com/thma/LtuPatternFactory#fluent-api--comonad | |
* | |
* In Builder pattern we have several pieces: | |
* A data type for the configuration. | |
* A data type for the value created from the configuration. | |
* A function that creates value from the configuration. | |
* A way to compose builders. | |
*/ | |
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<Config, Value> { | |
_tag: URI; | |
(opts: Config): Value; | |
} | |
export type BuildWith<Config, Value> = (b: Builder<Config, Value>) => Value; | |
export const makeBuilder = <Config, Value>(fn: FunctionN<[Config], Value>): Builder<Config, Value> => { | |
const b = fn as Builder<Config, Value>; | |
b._tag = URI; | |
return b; | |
}; | |
export const getComonadBuilder = <Config>(M: Monoid<Config>): Comonad2C<URI, Config> => ({ | |
URI, | |
_E: M.empty, | |
map: (fa, f) => makeBuilder((pa) => f(fa(pa))), | |
extract: (builder) => builder(M.empty), | |
extend: (builder, setter) => makeBuilder((opts2) => setter(makeBuilder((opts1) => builder(M.concat(opts2, opts1))))), | |
}); | |
type User = { | |
age: number; | |
phone: string; | |
address: string; | |
}; | |
type UserConfig = Partial<User>; | |
const monoidUserConfig: Monoid<Partial<User>> = { | |
concat: (userA, userB) => ({ | |
...userA, | |
...userB, | |
}), | |
empty: {}, | |
}; | |
const comonad = getComonadBuilder(monoidUserConfig); | |
const { extend } = pipeable(comonad); | |
const buildUser = makeBuilder<UserConfig, User>((config) => ({ age: 0, phone: '', address: '', ...config })); | |
const withAge: BuildWith<UserConfig, User> = (builder) => builder({ age: 30 }); | |
const withPhone: BuildWith<UserConfig, User> = (builder) => builder({ phone: '123123' }); | |
const withAddress: BuildWith<UserConfig, User> = (builder) => builder({ address: '123 skip street' }); | |
const withAdd10Years: BuildWith<UserConfig, User> = (builder) => { | |
const user = comonad.extract(builder); | |
return { ...user, age: user.age + 10 }; | |
}; | |
const user1 = pipe( | |
buildUser, | |
extend(withAge), | |
extend(withPhone), | |
extend(withAddress), | |
extend(withAdd10Years), | |
comonad.extract, | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment