-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add PartialOrd, Ord and Option
- Loading branch information
1 parent
4cab899
commit 57f6a1e
Showing
3 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
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
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)), | ||
}); |
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
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, | ||
}; |
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
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); | ||
}, | ||
}); |