fp-booleans
Version:
Utilities to apply boolean logic (not, and, or) to functions and higher-order functions. Tiny, tested and composable
81 lines (78 loc) • 2.95 kB
JavaScript
;
// TYPES
Object.defineProperty(exports, "__esModule", { value: true });
exports.isHigherOrderPredicate = exports.isPredicate = void 0;
exports.not = not;
exports.and = and;
exports.or = or;
// UTILS
/** Checking at runtime if a function is a predicate is difficult without parsing it.
The only way is to invoke it and check the type of the return value to be boolean.
To be able to invoke any function of any arity and arguments type, the only option invoke it without passing arguments.
Therefore, the predicate being tested must be resilient to be invoked without arguments.
Example:
BAD ❌ const hasLengthGreaterThanZero = (argument: string) => argument.length > 0;
Invoking it without arguments would throw "Cannot read properties of undefined (reading 'length')"
GOOD ✅ const hasLengthGreaterThanZero = (argument: string) => argument?.length > 0;
The return value being true or false, wrong or right, is not important. Must be a boolean and don't throw errors
when invoked without arguments.
*/
const isPredicate = (arg) => {
if (typeof arg !== 'function') {
return false;
}
let result;
try {
result = arg();
}
catch (e) {
throw new Error(`The argument predicate must be resilient to be invoked without parameters. Original error: ${e.message}`);
}
return typeof result === 'boolean';
};
exports.isPredicate = isPredicate;
const isHigherOrderPredicate = (arg) => {
if (typeof arg !== 'function') {
return false;
}
let partiallyApplied;
try {
partiallyApplied = arg();
}
catch (e) {
throw new Error(`The argument predicate must be resilient to be invoked without parameters. Original error: ${e.message}`);
}
return (0, exports.isPredicate)(partiallyApplied);
};
exports.isHigherOrderPredicate = isHigherOrderPredicate;
function not(arg) {
if (typeof arg === 'boolean') {
return !arg;
}
if (typeof arg === 'function') {
if ((0, exports.isPredicate)(arg)) {
return (...args) => !arg(...args);
}
if ((0, exports.isHigherOrderPredicate)(arg)) {
return (...higherOrderArgs) => not(arg(...higherOrderArgs));
}
throw new Error(`Argument Error: The argument must be a boolean, a predicate or a higher-order predicate`);
}
}
// and() & or() utils
const isBooleanArray = (value) => value.every((item) => typeof item === 'boolean');
const isPredicateArray = (value) => value.every((item) => typeof item === 'function');
const iterate = (method) => (...args) => {
if (isBooleanArray(args)) {
return method.call(args, Boolean);
}
if (isPredicateArray(args)) {
return (...predicatesArgs) => method.call(args, (f) => f(...predicatesArgs));
}
};
function and(...args) {
return iterate(Array.prototype.every)(...args);
}
function or(...args) {
return iterate(Array.prototype.some)(...args);
}