/**
* This file contains all of the tools for creating and
* composing Sortables. Since an Sortable encapsulates partial
* equality, the tools in this file should concern
* itself with sorting according to an Ordering as well
*
* @since 2.0.0
*/
import type { Hold, In, Kind } from "./kind.ts";
/**
* The ordering type is the expected output of any
* Compare function. The canonical example is the output
* of the Array.sort function. For any two values `first`
* and `second`, Ordering means the following:
*
* * -1 : first < second
* * 0 : first = second
* * 1 : first > second
*
* @since 2.0.0
*/
export type Ordering = -1 | 0 | 1;
/**
* The Sort function takes to values of the same
* type and returns an ordering, indicating whether
* `first` is less than, equal to, or greater than
* `second. See Ordering for the order.
*
* @since 2.0.0
*/
export type Sort = (first: A, second: A) => Ordering;
/**
* A Sortable structure has the method sort.
*
* @since 2.0.0
*/
export interface Sortable extends Hold {
readonly sort: Sort;
}
/**
* Specifies Sortable as a Higher Kinded Type, with
* contravariant parameter D corresponding to the 0th
* index of any Substitutions.
*
* @since 2.0.0
*/
export interface KindSortable extends Kind {
readonly kind: Sortable>;
}
/**
* Returns an Ordering from any number according
* to its relationship with 0.
*
* @example
* ```ts
* import { sign } from "./sortable.ts";
*
* const result1 = sign(-9586); // -1
* const result2 = sign(-0.005); // -1
* const result3 = sign(1000); // 1
* const result4 = sign(Number.NEGATIVE_INFINITY); // -1
* const result5 = sign(0); // 0
* ```
*
* @since 2.0.0
*/
export function sign(n: number): Ordering {
return n < 0 ? -1 : n > 0 ? 1 : 0;
}
/**
* Construct a curried less than function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const lt = O.lt(SortableNumber);
*
* const result1 = pipe(1, lt(2)); // true
* const result2 = pipe(2, lt(1)); // false
* const result3 = pipe(1, lt(1)); // false
* ```
*
* @since 2.0.0
*/
export function lt({ sort }: Sortable): (snd: A) => (fst: A) => boolean {
return (snd) => (fst): boolean => sort(fst, snd) === -1;
}
/**
* Construct a curried less than or equal to function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const lte = O.lte(SortableNumber);
*
* const result1 = pipe(1, lte(2)); // true
* const result2 = pipe(2, lte(1)); // false
* const result3 = pipe(1, lte(1)); // true
* ```
*
* @since 2.0.0
*/
export function lte({ sort }: Sortable): (snd: A) => (fst: A) => boolean {
return (snd) => (fst) => sort(fst, snd) !== 1;
}
/**
* Construct a curried greater than or equal to function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const gte = O.gte(SortableNumber);
*
* const result1 = pipe(1, gte(2)); // false
* const result2 = pipe(2, gte(1)); // true
* const result3 = pipe(1, gte(1)); // true
* ```
*
* @since 2.0.0
*/
export function gte({ sort }: Sortable): (snd: A) => (fst: A) => boolean {
return (snd) => (fst) => sort(fst, snd) !== -1;
}
/**
* Construct a curried greater than function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const gt = O.gt(SortableNumber);
*
* const result1 = pipe(1, gt(2)); // false
* const result2 = pipe(2, gt(1)); // true
* const result3 = pipe(1, gt(1)); // false
* ```
*
* @since 2.0.0
*/
export function gt({ sort }: Sortable): (snd: A) => (fst: A) => boolean {
return (snd) => (fst) => sort(fst, snd) === 1;
}
/**
* Construct a minimum function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const min = O.min(SortableNumber);
*
* const result1 = pipe(1, min(2)); // 1
* const result2 = pipe(2, min(1)); // 1
* const result3 = pipe(1, min(1)); // 1
* ```
*
* @since 2.0.0
*/
export function min({ sort }: Sortable): (snd: A) => (fst: A) => A {
return (snd) => (fst) => sort(fst, snd) !== 1 ? fst : snd;
}
/**
* Construct a maximum function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const max = O.max(SortableNumber);
*
* const result1 = pipe(1, max(2)); // 2
* const result2 = pipe(2, max(1)); // 2
* const result3 = pipe(1, max(1)); // 1
* ```
*
* @since 2.0.0
*/
export function max({ sort }: Sortable): (snd: A) => (fst: A) => A {
return (snd) => (fst) => sort(fst, snd) !== -1 ? fst : snd;
}
/**
* Construct an inclusive clamp function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
*
* const clamp = O.clamp(SortableNumber);
* const clamp1 = clamp(0, 10)
*
* const result1 = clamp1(-1); // 0
* const result2 = clamp1(1); // 1
* const result3 = clamp1(100); // 10
* ```
*
* @since 2.0.0
*/
export function clamp(
sort: Sortable,
): (low: A, high: A) => (value: A) => A {
const _min = min(sort);
const _max = max(sort);
return (low, high) => {
const __min = _min(high);
const __max = _max(low);
return (value) => __min(__max(value));
};
}
/**
* Construct an exclusive between function over A from Sortable.
*
* @example
* ```ts
* import * as O from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
*
* const between = O.between(SortableNumber);
* const between1 = between(0, 10)
*
* const result1 = between1(-1); // false
* const result2 = between1(1); // true
* const result3 = between1(100); // false
* ```
*
* @since 2.0.0
*/
export function between(
sort: Sortable,
): (low: A, high: A) => (value: A) => boolean {
const _gt = gt(sort);
const _lt = lt(sort);
return (low, high) => {
const __gt = _gt(low);
const __lt = _lt(high);
return (value) => __gt(value) && __lt(value);
};
}
/**
* Derives an Sortable from a Compare function.
*
* @example
* ```ts
* import { clamp, lte, min, fromSort, sign } from "./sortable.ts";
* import { pipe } from "./fn.ts";
*
* const date = fromSort(
* (fst, snd) => sign(fst.valueOf() - snd.valueOf())
* );
*
* const now = new Date();
* const later = new Date(Date.now() + 60 * 60 * 1000);
* const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
*
* const result1 = pipe(now, lte(date)(later)); // true
* const result2 = pipe(tomorrow, clamp(date)(now, later)); // later
* const result3 = pipe(tomorrow, min(date)(now)); // now
* ```
*
* @since 2.0.0
*/
export function fromSort(sort: Sort): Sortable {
return { sort };
}
/**
* Create a Sortable from a curried Sort function.
*
* @example
* ```ts
* import { fromCurriedSort } from "./sortable.ts";
*
* const numberSortable = fromCurriedSort(
* (second) => (first) => first < second ? -1 : first > second ? 1 : 0
* );
* const result = numberSortable.sort(3, 5); // -1
* ```
*
* @since 2.0.0
*/
export function fromCurriedSort(
sort: (second: A) => (first: A) => Ordering,
): Sortable {
return fromSort((first, second) => sort(second)(first));
}
/**
* Create a trivial Sortable, where all values of A are considered equal.
*
* @example
* ```ts
* import { lt, trivial } from "./sortable.ts";
* import { pipe } from "./fn.ts";
*
* const date = trivial();
* const now = new Date();
* const later = new Date(Date.now() + 60 * 60 * 1000);
*
* const lessThan = lt(date);
*
* const result1 = pipe(now, lessThan(later)); // false
* const result2 = pipe(later, lessThan(now)); // false
* const result3 = date.sort(now, later); // 1
* ```
*
* @since 2.0.0
*/
export function trivial(): Sortable {
return fromSort(() => 0);
}
/**
* Derive an Sortable with the reverse ordering of an existing Sortable.
*
* @example
* ```ts
* import { reverse, lt } from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* const rev = reverse(SortableNumber);
*
* const result1 = pipe(1, lt(rev)(2)); // false
* const result2 = pipe(2, lt(rev)(1)); // true
* ```
*
* @since 2.0.0
*/
export function reverse({ sort }: Sortable): Sortable {
return fromSort((first, second) => sort(second, first));
}
/**
* Derives an Sortable from a tuple of Sortables. The derived Sortable will compare
* two tuples starting at index 0 and return the first ordering
* that is non-zero, otherwise the two tuples are equal.
*
* @example
* ```ts
* import { tuple, lt } from "./sortable.ts"
* import { SortableNumber } from "./number.ts";
* import { SortableString } from "./string.ts";
* import { pipe } from "./fn.ts";
*
* const tup = tuple(SortableNumber, SortableString);
*
* const result1 = pipe([1, "a"], lt(tup)([2, "b"])); // true
* const result2 = pipe([1, "a"], lt(tup)([1, "b"])); // true
* const result3 = pipe([1, "a"], lt(tup)([1, "a"])); // false
* ```
*
* @since 2.0.0
*/
export function tuple>(
...sorts: { [K in keyof T]: Sortable }
): Sortable> {
return fromSort((a, b) => {
for (let i = 0; i < sorts.length; i++) {
const ordering = sorts[i].sort(a[i], b[i]);
if (ordering !== 0) return ordering;
}
return 0;
});
}
/**
* Derives an Sortable from a structs of Sortables. The derived Sortable will compare
* two structs starting with the first defined key and return the first
* ordering that is non-zero, otherwise the two structs are equal.
*
* @example
* ```ts
* import { struct, lt } from "./sortable.ts"
* import { SortableNumber } from "./number.ts";
* import { SortableString } from "./string.ts";
* import { pipe } from "./fn.ts";
*
* const ord = struct({ num: SortableNumber, str: SortableString });
* const _lt = lt(ord);
*
* const result1 = pipe(
* { num: 1, str: "a" },
* _lt({ str: "b", num: 2 })
* ); // true
* const result2 = pipe(
* { num: 1, str: "a" },
* _lt({ str: "b", num: 1 })
* ); // true
* const result3 = pipe(
* { num: 1, str: "a" },
* _lt({ str: "a", num: 1 })
* ); // false
*
* ```
*
* @since 2.0.0
*/
export function struct(
sorts: { readonly [K in keyof A]: Sortable },
): Sortable<{ readonly [K in keyof A]: A[K] }> {
return fromSort((fst, snd) => {
for (const key in sorts) {
const ordering = sorts[key].sort(fst[key], snd[key]);
if (ordering !== 0) return ordering;
}
return 0;
});
}
/**
* Derives an instance of Sortable by take an existing Sortable over D and
* a function that turns an L into D and returns an Sortable over L.
*
* @example
* ```ts
* import { premap } from "./sortable.ts";
* import { SortableNumber } from "./number.ts";
* import { pipe } from "./fn.ts";
*
* // Use number ordering, turn date into number and premap
* const date = pipe(
* SortableNumber,
* premap((d: Date) => d.valueOf()),
* );
* ```
*
* @since 2.0.0
*/
export function premap(
fld: (l: L) => D,
): ({ sort }: Sortable) => Sortable {
return ({ sort }) => fromSort((fst, snd) => sort(fld(fst), fld(snd)));
}