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