Skip to content

Instantly share code, notes, and snippets.

@KEIII
Last active January 16, 2021 17:06
Show Gist options
  • Save KEIII/c4cc15ae391f5e9e330185fc6d43f4ba to your computer and use it in GitHub Desktop.
Save KEIII/c4cc15ae391f5e9e330185fc6d43f4ba to your computer and use it in GitHub Desktop.
Maybe (Functor, Applicative & Monad) for TypeScript
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));
});
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))
);
});
});
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