208 lines
5.4 KiB
TypeScript
208 lines
5.4 KiB
TypeScript
/**
|
|
* Functions that does nothing (useful for default callbacks)
|
|
*/
|
|
export function nop(...args: any[]): any {
|
|
}
|
|
|
|
/**
|
|
* Identity function (returns the sole argument untouched)
|
|
*/
|
|
export function identity<T>(input: T): T {
|
|
return input;
|
|
}
|
|
|
|
/**
|
|
* Partially apply fixed arguments to a function with named arguments
|
|
*/
|
|
export function partial<A extends object, PA extends Partial<A>, RA extends any[], R>(fixedargs: PA, func: (args: A, ...rest: RA) => R): (args: Omit<A, keyof PA>, ...rest: RA) => R {
|
|
return (args: Omit<A, keyof PA>, ...rest: RA) => func({ ...fixedargs, ...args } as any, ...rest);
|
|
}
|
|
|
|
type cmpArgs = Readonly<{ key: (item: any) => any, reverse: boolean }>
|
|
const cmpDefaults: cmpArgs = { key: identity, reverse: false }
|
|
/**
|
|
* Compare operator, that can be used in sort() calls.
|
|
*/
|
|
export function cmp(args?: Partial<cmpArgs>): (a: any, b: any) => number {
|
|
const fargs = { ...cmpDefaults, ...args };
|
|
return (a, b) => {
|
|
const ka = fargs.key(a);
|
|
const kb = fargs.key(b);
|
|
if (ka > kb) {
|
|
return fargs.reverse ? -1 : 1;
|
|
} else if (ka < kb) {
|
|
return fargs.reverse ? 1 : -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Predicate that always returns true
|
|
*/
|
|
export const always = (...args: any[]) => true
|
|
|
|
/**
|
|
* Predicate that always returns false
|
|
*/
|
|
export const never = (...args: any[]) => false
|
|
|
|
/**
|
|
* Negate a predicate
|
|
*/
|
|
export function not<A extends any[]>(predicate: (...args: A) => boolean): (...args: A) => boolean {
|
|
return (...args) => !predicate(...args);
|
|
}
|
|
|
|
/**
|
|
* Apply a boolean "and" to merge predicates
|
|
*/
|
|
export function and<A extends any[]>(...predicates: ((...args: A) => boolean)[]): (...args: A) => boolean {
|
|
return (...args) => {
|
|
for (let p of predicates) {
|
|
if (!p(...args)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply a boolean "or" to merge predicates
|
|
*/
|
|
export function or<A extends any[]>(...predicates: ((...args: A) => boolean)[]): (...args: A) => boolean {
|
|
return (...args) => {
|
|
for (let p of predicates) {
|
|
if (p(...args)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a function to check the strict equality with a reference
|
|
*/
|
|
export function is<T, S extends T>(ref: T): (input: S) => input is Extract<T, S> {
|
|
return (x): x is Extract<T, S> => x === ref;
|
|
}
|
|
|
|
/**
|
|
* Get a function to check that inputs are instances of a given type
|
|
*/
|
|
export function isinstance<T, S extends T>(ref: { new(...args: any[]): T }): (input: S) => input is Extract<T, S> {
|
|
return (x): x is Extract<T, S> => x instanceof ref;
|
|
}
|
|
|
|
/**
|
|
* Pipe the result of a function as first parameter of another, to create a new function
|
|
*/
|
|
export function pipe<IN extends any[], INT, R>(f1: (...args: IN) => INT, f2: (value: INT) => R): (...args: IN) => R {
|
|
return (...args: IN) => f2(f1(...args));
|
|
}
|
|
|
|
/**
|
|
* Attribute getter
|
|
*/
|
|
export function attr<K extends string>(name: K): <O extends Record<K, O[K]>>(obj: O) => O[K] {
|
|
return obj => obj[name];
|
|
}
|
|
|
|
/**
|
|
* Index getter
|
|
*/
|
|
export function at<T>(idx: number): (array: ReadonlyArray<T>) => T | undefined {
|
|
if (idx < 0) {
|
|
return array => array[array.length + idx];
|
|
} else {
|
|
return array => array[idx];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Array first item getter
|
|
*/
|
|
export const first: <T extends ReadonlyArray<any>>(array: T) => T[0] | undefined = at(0);
|
|
|
|
/**
|
|
* Array second item getter
|
|
*/
|
|
export const second: <T extends ReadonlyArray<any>>(array: T) => T[1] | undefined = at(1);
|
|
|
|
/**
|
|
* Array third item getter
|
|
*/
|
|
export const third: <T extends ReadonlyArray<any>>(array: T) => T[2] | undefined = at(2);
|
|
|
|
/**
|
|
* Array last item getter
|
|
*/
|
|
export const last: <T>(array: ReadonlyArray<T>) => T | undefined = at(-1);
|
|
|
|
/**
|
|
* Check that a value is not null, throwing an error if it is
|
|
*/
|
|
export function nn<T>(value: T | null): T {
|
|
if (value === null) {
|
|
throw new Error("null value");
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that a value is not undefined, throwing an error if it is
|
|
*/
|
|
export function nu<T>(value: T | undefined): T {
|
|
if (typeof value === "undefined") {
|
|
throw new Error("undefined value");
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that a value is not null nor undefined, throwing an error if it is
|
|
*/
|
|
export const nnu: <T>(value: T | null | undefined) => T = pipe(nn, nu);
|
|
|
|
/**
|
|
* Convert a value to a boolean (are considered falsy: 0, false, "", {}, [], null, undefined)
|
|
*/
|
|
export function bool<T>(value: T | null | undefined): value is T;
|
|
export function bool(value: any): boolean {
|
|
if (!value) {
|
|
return false;
|
|
} else if (value instanceof Set) {
|
|
return value.size > 0;
|
|
} else if (typeof value == "object") {
|
|
return Object.keys(value).length > 0;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies map and filter on an array, using a single function
|
|
*/
|
|
export function fmap<A, T extends A>(m?: undefined, f?: (val: A) => val is T): (array: A[]) => T[];
|
|
export function fmap<A>(m?: undefined, f?: (val: A) => boolean): (array: A[]) => A[];
|
|
export function fmap<A, B, T extends B>(m: (val: A) => B, f?: (val: B) => val is T): (array: A[]) => T[];
|
|
export function fmap<A, B>(m: (val: A) => B, f?: (val: B) => boolean): (array: A[]) => B[];
|
|
export function fmap<A, B>(m?: (val: A) => B, f?: (val: A | B) => boolean): (array: A[]) => (A | B)[] {
|
|
return array => {
|
|
if (m && f) {
|
|
return array.map(m).filter(f);
|
|
} else if (m) {
|
|
return array.map(m);
|
|
} else if (f) {
|
|
return array.filter(f);
|
|
} else {
|
|
return array.slice();
|
|
}
|
|
};
|
|
}
|