functional/src/index.ts

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();
}
};
}