import * as Type from '../core/Type'; import * as ArrayUtil from '../core/ArrayUtil'; /** A way of comparing two values of the same type for equality. */ export interface Eq { eq: (x: A, y: A) => boolean; } export const contramap = (eqa: Eq, f: (b: B) => A): Eq => eq((x, y) => eqa.eq(f(x), f(y))); export const eq = (f: (x: A, y: A) => boolean): Eq => ({ eq: f }); export const tripleEq: Eq = eq((x, y) => x === y); export const eqString: Eq = tripleEq; export const eqBoolean: Eq = tripleEq; export const eqNumber: Eq = tripleEq; export const eqUndefined: Eq = tripleEq; export const eqNull: Eq = tripleEq; export const eqArray = (eqa: Eq): Eq> => eq((x, y) => { if (x.length !== y.length) { return false; } const len = x.length; for (let i = 0; i < len; i++) { if (!eqa.eq(x[i], y[i])) { return false; } } return true; }); // TODO: Make an Ord typeclass const eqSortedArray = (eqa: Eq, compareFn?: (a: A, b: A) => number): Eq> => contramap(eqArray(eqa), (xs) => ArrayUtil.sort(xs, compareFn)); export const eqRecord = (eqa: Eq): Eq> => eq((x, y) => { const kx = Object.keys(x); const ky = Object.keys(y); if (!eqSortedArray(eqString).eq(kx, ky)) { return false; } const len = kx.length; for (let i = 0; i < len; i++) { const q = kx[i]; if (!eqa.eq(x[q], y[q])) { return false; } } return true; }); export const eqAny: Eq = eq((x, y) => { if (x === y) { return true; } const tx = Type.typeOf(x); const ty = Type.typeOf(y); if (tx !== ty) { return false; } if (Type.isEquatableType(tx)) { return x === y; } else if (tx === 'array') { return eqArray(eqAny).eq(x, y); } else if (tx === 'object') { return eqRecord(eqAny).eq(x, y); } return false; });