UNPKG

atomic-fns

Version:

Like Lodash, but for ESNext and with types. Stop shipping code built for browsers from 2015.

302 lines (301 loc) 8.74 kB
/** * Generic operations for both values and objects. * * @module operators */ import { call, isObject } from '../globals/index.js'; /** This is the identity function. It always returns the same value that was passed in */ export function id(x) { return x; } /** * Called to implement truth value testing. * @param x * @returns {boolean} `x.bool()` if exists or `!!x`. */ export function bool(x) { const r = call(x, 'bool'); if (r != null) return r; return !!x; } /** * Returns `true` if `x` is a falsy value. * @see {@link bool} */ export function not(x) { return !bool(x); } /** * Returns `true` if `x` is an instance of class `y` * @param x The instance object * @param y The parent class * @returns {boolean} */ export function isinstance(x, y) { return x instanceof y; } /** * Checks whether `x` is a comparable type and returns the result of `x.compare(y)`. * Otherwise the return value of the compare function is equivalent to `obj === other ? 0 : obj < other ? -1 : 1` * @param {*} x An initial value * @param {*} y Other value to compare * @returns {number} The comparison result * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters Array.sort()} * @see {@link operators.eq} * @see {@link operators.lt} */ export function compare(x, y) { if (x === y || eq(x, y)) return 0; const op = call(x, 'compare', y); if (typeof op === 'number') return op; if (x < y || lt(x, y)) return -1; return 1; } /** * Compares two values to check if they are the same. If `x` is an object with an `eq` method, it returns `x.eq(y)`. * Otherwise it returns the result of `x === y`. * @param {*} x * @param {*} y * @returns {boolean} `true` if `x` and `y` are equal or `false` otherwise. */ export function eq(x, y) { return Object.is(x, y) || call(x, 'eq', y) != null; } /** * Compares two values to check if `x` is strictly less than `y`. If `x` is an object with a `lt` method, it returns `x.lt(y)`. * Otherwise it returns the result of `x < y`. * @param {*} x * @param {*} y * @returns {boolean} `true` if `x` is strictly less than `y` or `false` otherwise. */ export function lt(x, y) { return x < y || call(x, 'lt', y) != null; } /** * Compares two values to check if `x <= y`. If `x` is an object with a `lte` method, it returns `x.lte(y)`, also checks `x.eq(y)` and `x.lt(y)`. * Otherwise it returns the result of `x <= y`. * @param {*} x * @param {*} y * @returns {boolean} `true` if `x` is less than or equal to `y` or `false` otherwise. * @see {@link eq} * @see {@link lt} */ export function lte(x, y) { if (x <= y || call(x, 'lte', y)) return true; if (x === y || eq(x, y)) return true; if (x < y || lt(x, y)) return true; return false; } /** * Compares two values to check if `x` is strictly greater than `y`. If `x` is an object with a `gt` method, it returns `x.gt(y)`. * Otherwise it returns the result of `x > y`. * @param {*} x * @param {*} y * @returns {boolean} `true` if `x` is strictly greater than `y` or `false` otherwise. */ export function gt(x, y) { return x > y || call(x, 'gt', y); } /** * Compares two values to check if `x >= y`. If `x` is an object with a `gte` method, it returns `x.gte(y)`, also checks `x.eq(y)` and `x.gt(y)`. * Otherwise it returns the result of `x >= y`. * @param {*} x * @param {*} y * @returns {boolean} `true` if `x` is less greater than or equal to `y` or `false` otherwise. * @see {@link eq} * @see {@link gt} */ export function gte(x, y) { if (x <= y || call(x, 'gte', y)) return true; if (x === y || eq(x, y)) return true; if (x > y || gt(x, y)) return true; return false; } /** * The addition (+) operator. If `x` is an object with an `add` method, it returns `x.add(y)`. * Otherwise it returns the result of `x + y`. * @param {*} x * @param {*} y * @returns {*} x + y */ export function add(x, y) { const op = call(x, 'add', y); if (op != null) return op; return x + y; } /** * The subtraction (-) operator. If `x` is an object with a `sub` method, it returns `x.sub(y)`. * Otherwise it returns the result of `x - y`. * @param {*} x * @param {*} y * @returns {*} x - y */ export function sub(x, y) { const op = call(x, 'sub', y); if (op != null) return op; return x - y; } /** * The multiplication (*) operator. If `x` is an object with an `mul` method, it returns `x.mul(y)`. * Otherwise it returns the result of `x * y`. * @param {*} x * @param {*} y * @returns {*} x * y */ export function mul(x, y) { const op = call(x, 'mul', y); if (op != null) return op; return x * y; } /** * The division (/) operator. If `x` is an object with a `div` method, it returns `x.div(y)`. * Otherwise it returns the result of `x / y`. * @param {*} x * @param {*} y * @returns {*} x / y */ export function div(x, y) { const op = call(x, 'div', y); if (op != null) return op; return x / y; } /** * The modulo (%) operator. If `x` is an object with a `mod` method, it returns `x.mod(y)`. * Otherwise it returns the result of `x % y`. * @param {*} x * @param {*} y * @returns {*} x % y */ export function mod(x, y) { const op = call(x, 'mod', y); if (op != null) return op; return x % y; } /** * The power (**) operator. If `x` is an object with a `pow` method, it returns `x.pow(y)`. * Otherwise it returns the result of `x ** y`. * @param {*} x * @param {*} y * @returns {*} x ** y */ export function pow(x, y) { const op = call(x, 'pow', y); if (op != null) return op; return x ** y; } /** * Performs a shallow comparison of two objects or arrays to check if they have the same keys, length and values. * @param obj * @param other * @returns `true` if the objects are considered equal. * @see {@link deepEqual} */ export function shallowEqual(obj, other) { if (obj === other) return true; if (!obj || !other) return false; if (Array.isArray(obj)) { // compare the arrays if (obj.length !== other.length) return false; for (let i = 0; i < obj.length; i++) { if (obj[i] !== other[i]) { return false; } } return true; } else if (isObject(obj)) { // compare the object keys const objKeys = Object.keys(obj); const otherKeys = Object.keys(other); if (objKeys.length !== otherKeys.length) return false; for (const key of objKeys) { if (!Object.prototype.hasOwnProperty.call(other, key) || obj[key] !== other[key]) { return false; } } return true; } return false; } export function deepEqual(obj, other, checker = eq, seen = new WeakSet()) { if (checker(obj, other)) return true; if (!obj || !other) return false; if (obj.prototype !== other.prototype) return false; if (seen.has(obj) && seen.has(other)) return false; if (Array.isArray(obj)) { // compare the arrays if (obj.length !== other.length) return false; for (let i = 0; i < obj.length; i++) { if (!deepEqual(obj[i], other[i], checker)) { return false; } } return true; } if (obj instanceof Set) { if (obj.size !== other.size) return false; for (const value of obj.values()) { if (!other.has(value)) return false; } return true; } if (obj instanceof Map) { // compare keys and values if (obj.size !== other.size) return false; seen.add(obj); seen.add(other); for (const key of obj.keys()) { if (!other.has(key)) return false; if (!deepEqual(obj.get(key), other.get(key), checker, seen)) { return false; } } return true; } if (isObject(obj)) { // compare the object own enumerable keys const objKeys = Object.keys(obj); const otherKeys = Object.keys(other); if (objKeys.length !== otherKeys.length) return false; seen.add(obj); seen.add(other); for (const key of objKeys) { if (!Object.prototype.hasOwnProperty.call(other, key) || !deepEqual(obj[key], other[key], checker, seen)) { return false; } } return true; } return false; }