Skip to content

Instantly share code, notes, and snippets.

@WimJongeneel
Last active February 21, 2020 21:33
Show Gist options
  • Save WimJongeneel/6ba3164957e3f1639f7fac150a7f95b2 to your computer and use it in GitHub Desktop.
Save WimJongeneel/6ba3164957e3f1639f7fac150a7f95b2 to your computer and use it in GitHub Desktop.
Funtors and fmap in TypeScript
// functors
type Option<a> = { kind: 'some', value: a } | { kind: 'none' }
type Result<a, e> = { kind: 'success', value: a } | { kind: 'error', error: e }
type List<a> = { value: a, next: List<a> | null }
type Reader<a, i> = (i: i) => a
// units
const some = <a>(value: a): Option<a> => ({ kind: 'some', value })
const success = <a, e>(value: a): Result<a, e> => ({ kind: 'success', value })
const list_of = <a>(value:a): List<a> => ({value, next: null})
const reader = <a, i>(value:a): Reader<a, i> => (i:i) => value
// non-unit constructors
const none = <a>(): Option<a> => ({ kind: 'none' })
const fail = <a, e>(error: e): Result<a, e> => ({ kind: 'error', error })
// map
const map_option = <a, b>(f: (a:a) => b) => (o:Option<a>): Option<b> =>
o.kind == 'some' ? some(f(o.value)) : none()
const map_result = <a, b, e>(f:(a:a) => b) => (r: Result<a, e>): Result<b, e> =>
r.kind == 'success' ? success(f(r.value)) : r
const map_list = <a, b>(f:(a:a) => b) => (l: List<a>): List<b> =>
({ value: f(l.value), next: l.next ? map_list(f)(l.next) : null})
const map_reader = <a, b, i>(f:(a:a) => b) => (r:Reader<a, i>): Reader<b, i> =>
i => f(r(i))
// implemting the global fmap function
type FunctorMap<a> = {
list: List<a>
option: Option<a>
result: Result<a, any>
reader: Reader<a, any>
}
type Functor<a> = FunctorMap<a>[keyof FunctorMap<a>]
type FmapMapping<a, b> = { [k in keyof FunctorMap<any>]: (f: (_: a) => b) => (_: FunctorMap<a>[k]) => FunctorMap<b>[k] }
const fmaping: <a, b>() => FmapMapping<a, b> = () => ({
list: f => l => map_list(f)(l),
option: f => o => map_option(f)(o),
reader: f => r => map_reader(f)(r),
result: f => r => map_result(f)(r)
})
type FunctorTypeGuards = { [k in keyof FunctorMap<any>]: (f: any) => f is FunctorMap<any>[k] }
const functorTypeGuards: FunctorTypeGuards = {
list: (l): l is List<any> => Object.keys(l).indexOf('value') != -1 && Object.keys(l).indexOf('next') != -1,
option: (o): o is Option<any> => o.kind == 'some' || o.kind == 'none',
reader: (r): r is Reader<any, any> => typeof r == 'function',
result: (r): r is Result<any, any> => r.kind == 'success' || r.kind == 'error'
}
type MapFunctor<functor, to> =
functor extends List<any> ? List<to> :
functor extends Option<any> ? Option<to> :
functor extends Reader<any, infer i> ? Reader<to, i> :
functor extends Result<any, infer e> ? Result<to, e> :
never
const fmap = <a, b>(f: (a: a) => b) => <f extends Functor<a>>(f1: f): MapFunctor<typeof f1, b> => {
for (const g in functorTypeGuards) {
if (functorTypeGuards[g](f1)) return fmaping<a, b>()[g](f)(f1)
}
}
// tests
const map = fmap((n: boolean) => n.toString())
const map1 = map(some(true))
console.log(map1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment