Last active
January 16, 2021 17:06
-
-
Save KEIII/c4cc15ae391f5e9e330185fc6d43f4ba to your computer and use it in GitHub Desktop.
Maybe (Functor, Applicative & Monad) for TypeScript
This file contains hidden or 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 { Maybe, Nothing, Just } from "./maybe"; | |
test("composition", () => { | |
const f = (ma: Maybe<number>) => { | |
return ma | |
.flatMap((x) => (x % 2 === 0 ? Nothing : Just(x + 1))) | |
.map((x) => x * x); | |
}; | |
expect(f(Nothing)).toEqual(Nothing); | |
expect(f(Just(2))).toEqual(Nothing); | |
expect(f(Just(3))).toEqual(Just(16)); | |
}); |
This file contains hidden or 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 { Just } from './maybe'; | |
const of = Just; | |
describe('To fully qualify as a monad though, these three parts must also respect a few laws', () => { | |
// unit(a) >>= λx → f(x) ↔ f(a) | |
test('unit is a left-identity for bind', () => { | |
const f = (x: number | null) => { | |
if (x === null) { | |
x = -1; | |
} else if (x === 2) { | |
x = null; | |
} else { | |
x = x + 1; | |
} | |
return of(x); | |
}; | |
const a = null; | |
expect( | |
of(a).flatMap(f) | |
).toEqual( | |
f(a) | |
); | |
}); | |
// ma >>= λx → unit(x) ↔ ma | |
test('unit is also a right-identity for bind', () => { | |
expect( | |
of(42).flatMap(of) | |
).toEqual( | |
of(42) | |
); | |
}); | |
// ma >>= λx → (f(x) >>= λy → g(y)) ↔ (ma >>= λx → f(x)) >>= λy → g(y) | |
test('bind is essentially associative', () => { | |
const ma = of(42); | |
const f = (n: number) => of(n + 2); | |
const g = (n: number) => of(n - 3); | |
expect( | |
ma.flatMap(f).flatMap(g) | |
).toEqual( | |
ma.flatMap(x => f(x).flatMap(g)) | |
); | |
}); | |
}); |
This file contains hidden or 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
export type Nothing = { | |
readonly _tag: "Nothing"; | |
}; | |
export type Just<A> = { | |
readonly _tag: "Just"; | |
readonly value: A; | |
}; | |
type Functor<A> = { | |
readonly map: <B>(f: (v: A) => B) => Maybe<B>; | |
}; | |
type Applicative<A> = { | |
readonly ap: <B>(mf: Maybe<(v: A) => B>) => Maybe<B>; | |
}; | |
type Monad<A> = { | |
readonly flatMap: <B>(f: (v: A) => Maybe<B>) => Maybe<B>; | |
}; | |
export type Maybe<A> = (Nothing | Just<A>) & | |
Functor<A> & | |
Applicative<A> & | |
Monad<A>; | |
export const Nothing = (() => { | |
const map = () => m; | |
const proto = { map, ap: map, flatMap: map }; | |
const m = { _tag: "Nothing" }; | |
Object.setPrototypeOf(m, proto); | |
return m as Maybe<never>; | |
})(); | |
export const Just = <A>(value: A): Maybe<A> => { | |
const proto: Functor<A> & Applicative<A> & Monad<A> = { | |
map: (f) => Just(f(value)), | |
ap: (mf) => (mf._tag === "Just" ? Just(mf.value(value)) : Nothing), | |
flatMap: (f) => f(value), | |
}; | |
const m = { _tag: "Just", value }; | |
Object.setPrototypeOf(m, proto); | |
return m as Maybe<A>; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment