Skip to content

Commit

Permalink
feat: Add PartialOrd, Ord and Option
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina committed Aug 1, 2022
1 parent 4cab899 commit 57f6a1e
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 0 deletions.
190 changes: 190 additions & 0 deletions src/option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import type { Monad1 } from "./type-class/monad";
import type { Monoid } from "./type-class/monoid";

const someSymbol = Symbol("OptionSome");
const noneSymbol = Symbol("OptionNone");

export type Some<T> = readonly [typeof someSymbol, T];
export type None = readonly [typeof noneSymbol];

declare const optionNominal: unique symbol;
export type OptionHktKey = typeof optionNominal;
export type Option<T> = None | Some<T>;

export const some = <T>(v: T): Option<T> => [someSymbol, v];
export const none = <T>(): Option<T> => [noneSymbol];

export const fromPredicate =
<T>(predicate: (t: T) => boolean) =>
(t: T): Option<T> => {
if (predicate(t)) {
return some(t);
}
return none();
};

export const toString = <T>(opt: Option<T>) => (opt[0] === someSymbol ? `some(${opt[1]})` : `none`);
export const toArray = <T>(opt: Option<T>): T[] => {
const arr = [...opt] as unknown[];
arr.shift();
return arr as T[];
};

export const isSome = <T>(opt: Option<T>): opt is Some<T> => opt[0] === someSymbol;
export const isNone = <T>(opt: Option<T>): opt is None => opt[0] === noneSymbol;

export const flatten = <T>(opt: Option<Option<T>>): Option<T> => {
if (isSome(opt)) {
return opt[1];
}
return opt;
};

export const and =
<T>(optA: Option<T>) =>
(optB: Option<T>) => {
if (isSome(optA)) {
return optB;
}
return optA;
};
export const andThen =
<T>(optA: Option<T>) =>
(optB: () => Option<T>) => {
if (isSome(optA)) {
return optB();
}
return optA;
};
export const or =
<T>(optA: Option<T>) =>
(optB: Option<T>) => {
if (isSome(optA)) {
return optA;
}
return optB;
};
export const orElse =
<T>(optA: Option<T>) =>
(optB: () => Option<T>) => {
if (isSome(optA)) {
return optA;
}
return optB();
};
export const xor =
<T>(optA: Option<T>) =>
(optB: Option<T>) => {
if (isSome(optA) && isNone(optB)) {
return optA;
}
if (isNone(optA) && isSome(optB)) {
return optB;
}
return none<T>();
};

export const filter =
<T>(predicate: (t: T) => boolean) =>
(opt: Option<T>) => {
if (isSome(opt)) {
if (predicate(opt[1])) {
return opt;
}
}
return none<T>();
};

export const zip =
<T>(optA: Option<T>) =>
<U>(optB: Option<U>): Option<[T, U]> => {
if (isSome(optA) && isSome(optB)) {
return some([optA[1], optB[1]]);
}
return none();
};
export const unzip = <T, U>(opt: Option<[T, U]>): [Option<T>, Option<U>] => {
if (isSome(opt)) {
return [some(opt[1][0]), some(opt[1][1])];
}
return [none(), none()];
};
export const zipWith =
<T>(optA: Option<T>) =>
<U>(optB: Option<U>) =>
<R, F extends (t: T, u: U) => R>(fn: F): Option<R> => {
if (isSome(optA) && isSome(optB)) {
return some(fn(optA[1], optB[1]));
}
return none();
};

export const unwrapOr =
<T>(init: T) =>
(opt: Option<T>) => {
if (isSome(opt)) {
return opt[1];
}
return init;
};
export const unwrapOrElse =
<T>(fn: () => T) =>
(opt: Option<T>) => {
if (isSome(opt)) {
return opt[1];
}
return fn();
};

export const map =
<T>(opt: Option<T>) =>
<U>(f: (t: T) => U): Option<U> => {
if (opt[0] === someSymbol) {
return some(f(opt[1]));
}
return opt;
};
export const mapOr =
<U>(init: U) =>
<T>(opt: Option<T>) =>
(f: (t: T) => U): U => {
if (opt[0] === someSymbol) {
return f(opt[1]);
}
return init;
};
export const mapOrElse =
<U>(fn: () => U) =>
<T>(opt: Option<T>) =>
(f: (t: T) => U): U => {
if (opt[0] === someSymbol) {
return f(opt[1]);
}
return fn();
};

export const flatMap =
<T>(opt: Option<T>) =>
<U>(f: (t: T) => Option<U>): Option<U> => {
if (opt[0] === someSymbol) {
return f(opt[1]);
}
return opt;
};

declare module "./hkt" {
interface HktDictA1<A1> {
[optionNominal]: Option<A1>;
}
}

export const monoid = <T>(): Monoid<Option<T>> => ({
combine: (l, r) => or(l)(r),
identity: none(),
});

export const monad = <T>(): Monad1<OptionHktKey> => ({
pure: some,
flatMap: (f) => (opt) => flatMap(opt)(f),
apply: (fnOpt) => (tOpt) => map(zip(fnOpt)(tOpt))(([fn, t]) => fn(t)),
});
24 changes: 24 additions & 0 deletions src/ordering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Monoid } from "./type-class/monoid";

export const less = -1;
export const equal = 0;
export const greater = 1;
export type Ordering = typeof less | typeof equal | typeof greater;

export const isLt = (ord: Ordering) => ord === less;
export const isGt = (ord: Ordering) => ord === greater;
export const isLe = (ord: Ordering) => ord !== greater;
export const isGe = (ord: Ordering) => ord !== less;
export const isEq = (ord: Ordering) => ord == equal;
export const isNe = (ord: Ordering) => ord != equal;

export const reverse = (order: Ordering): Ordering => -order as Ordering;

export const then = (first: Ordering, second: Ordering) => (first === equal ? second : first);
export const thenWith = (first: Ordering, secondFn: () => Ordering) =>
first === equal ? secondFn() : first;

export const monoid: Monoid<Ordering> = {
combine: then,
identity: equal,
};
85 changes: 85 additions & 0 deletions src/type-class/ord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Eq, PartialEq, tuple as tupleEq } from "./eq";
import { flatMap, isNone, map, mapOr, none, Option, some } from "../option";
import { Ordering, equal, then, isEq } from "../ordering";
import type { Monoid } from "./monoid";
import { Contravariant } from "./variance";

declare const partialOrdNominal: unique symbol;
export type PartialOrdHktKey = typeof partialOrdNominal;

declare module "../hkt" {
interface HktDictA1<A1> {
[partialOrdNominal]: PartialOrd<A1, A1>;
}
}

/**
* All instances of `PartialOrd` must satisfy following conditions:
* - Transitivity: If `f` is `PartialOrd`, for all `a`, `b` and `c`; `f(a, b) == f(b, c) == f(a, c)`.
* - Duality: If `f` is `PartialOrd`, for all `a` and `b`; `f(a, b) == -f(b, a)`.
*/
export interface PartialOrd<Lhs, Rhs> extends PartialEq<Lhs, Rhs> {
partialCmp(lhs: Lhs, rhs: Rhs): Option<Ordering>;
}

export const fromPartialCmp = <Lhs, Rhs>(
partialCmp: (lhs: Lhs, rhs: Rhs) => Option<Ordering>,
): PartialOrd<Lhs, Rhs> => ({
partialCmp,
eq: (l, r) => mapOr(false)(partialCmp(l, r))((order: Ordering) => isEq(order)),
});

export const tuple = <T extends unknown[]>(ord: {
readonly [K in keyof T]: PartialOrd<T[K], T[K]>;
}): PartialOrd<T, T> => ({
partialCmp: (lhs, rhs) => {
const len = Math.min(lhs.length, rhs.length);
let result: Ordering = equal;
for (let i = 0; i < len; ++i) {
const order = ord[i].partialCmp(lhs[i], rhs[i]);
if (isNone(order)) {
return none();
}
result = then(result, order[1]);
}
return some(result);
},
eq: tupleEq(ord).eq,
});

export const contravariant: Contravariant<PartialOrdHktKey> = {
contraMap: (f) => (ord) => fromPartialCmp((l, r) => ord.partialCmp(f(l), f(r))),
};

export const identity: PartialOrd<unknown, unknown> = fromPartialCmp(() => some(equal));

export const monoid = <Lhs, Rhs>(): Monoid<PartialOrd<Lhs, Rhs>> => ({
combine: (x, y) => ({
partialCmp: (l, r) =>
flatMap(x.partialCmp(l, r))((first) =>
map(y.partialCmp(l, r))((second) => then(first, second)),
),
eq: (l, r) => x.eq(l, r) && y.eq(l, r),
}),
identity: identity,
});

/**
* All instances of `PartialOrd` must satisfy following conditions:
* - Transitivity: If `f` is `PartialOrd`, for all `a`, `b` and `c`; `f(a, b) == f(b, c) == f(a, c)`.
* - Duality: If `f` is `PartialOrd`, for all `a` and `b`; `f(a, b) == -f(b, a)`.
* - Strict: Ordering for all values is well-defined (so the return value is not an `Option`).
*/
export interface Ord<Lhs, Rhs> extends PartialOrd<Lhs, Rhs>, Eq<Lhs, Rhs> {
cmp(lhs: Lhs, rhs: Rhs): Ordering;
}

export const reversed = <Lhs, Rhs>(ord: Ord<Lhs, Rhs>): Ord<Lhs, Rhs> => ({
...ord,
cmp: (lhs, rhs) => {
return -ord.cmp(lhs, rhs) as Ordering;
},
partialCmp(lhs, rhs) {
return some(-ord.cmp(lhs, rhs) as Ordering);
},
});

0 comments on commit 57f6a1e

Please sign in to comment.