/**
* This file contains the ReadonlyMap algebraic data type. ReadonlyMap is a
* built in data structure in javascript that allows the association of
* arbitrary key anv value pairs. Generally, keys in a ReadonlyMap are only
* equal if their memory addresses are equal. This means that while strings and
* numbers work as expected as keys, complex objects such as [1] or { one: 1 }
* do not. Thus, there are many utility functions in this file that allow one to
* specify how to determine equality for keys in a ReadonlyMap.
*
* @module ReadonlyMap
* @since 2.0.0
*/
import type { Kind, Out } from "./kind.ts";
import type { Bimappable } from "./bimappable.ts";
import type { Combinable } from "./combinable.ts";
import type { Comparable } from "./comparable.ts";
import type { Flatmappable } from "./flatmappable.ts";
import type { Foldable } from "./foldable.ts";
import type { Initializable } from "./initializable.ts";
import type { Mappable } from "./mappable.ts";
import type { Option } from "./option.ts";
import type { Showable } from "./showable.ts";
import type { Sortable } from "./sortable.ts";
import * as O from "./option.ts";
import * as A from "./array.ts";
import { fromCompare } from "./comparable.ts";
import { fromCombine } from "./combinable.ts";
import { flow, pipe } from "./fn.ts";
/**
* @since 2.0.0
*/
export interface KindReadonlyMap extends Kind {
readonly kind: ReadonlyMap, Out>;
}
/**
* @since 2.0.0
*/
export interface KindKeyedReadonlyMap extends Kind {
readonly kind: ReadonlyMap>;
}
/**
* @since 2.0.0
*/
export type TypeOf = U extends ReadonlyMap ? A : never;
/**
* @since 2.0.0
*/
export type KeyOf = U extends ReadonlyMap ? B : never;
/**
* @since 2.0.0
*/
export function init(): ReadonlyMap {
return new Map();
}
/**
* @since 2.0.0
*/
export function singleton(k: K, a: A): ReadonlyMap {
return new Map([[k, a]]);
}
/**
* @since 2.0.0
*/
export function isEmpty(ta: ReadonlyMap): boolean {
return ta.size === 0;
}
/**
* @since 2.0.0
*/
export function readonlyMap(
...pairs: [K, A][]
): ReadonlyMap {
return new Map(pairs);
}
/**
* @since 2.0.0
*/
export function map(
fai: (a: A) => I,
): (ta: ReadonlyMap) => ReadonlyMap {
return (ta) => {
const tb = new Map();
for (const [k, a] of ta) {
tb.set(k, fai(a));
}
return tb;
};
}
/**
* @since 2.0.0
*/
export function bimap(
fbj: (b: B) => J,
fai: (a: A) => I,
): (ta: ReadonlyMap) => ReadonlyMap {
return (ta) => {
const tb = new Map();
for (const [b, a] of ta) {
tb.set(fbj(b), fai(a));
}
return tb;
};
}
/**
* @since 2.0.0
*/
export function mapSecond(
fbj: (b: B) => J,
): (ta: ReadonlyMap) => ReadonlyMap {
return (ta) => {
const tb = new Map();
for (const [b, a] of ta) {
tb.set(fbj(b), a);
}
return tb;
};
}
/**
* @since 2.0.0
*/
export function size(d: ReadonlyMap): number {
return d.size;
}
/**
* @since 2.0.0
*/
export function lookupWithKey(
S: Comparable,
): (k: K) => (ta: ReadonlyMap) => Option<[K, A]> {
return (k) => (ta) => {
const _compare = S.compare(k);
for (const [ka, a] of ta.entries()) {
if (_compare(ka)) {
return O.some([ka, a]);
}
}
return O.none;
};
}
/**
* @since 2.0.0
*/
export function lookup(
S: Comparable,
): (k: K) => (ta: ReadonlyMap) => Option {
const _lookupWithKey = lookupWithKey(S);
return (k) => {
const lookupWithKeyE = _lookupWithKey(k);
return (ta) => {
return pipe(
lookupWithKeyE(ta),
O.map(([_, a]) => a),
);
};
};
}
/**
* @since 2.0.0
*/
export function member(
S: Comparable,
): (k: K) => (ta: ReadonlyMap) => boolean {
const lookupKey = lookup(S);
return (k) => (m) => pipe(lookupKey(k)(m), O.isSome);
}
/**
* @since 2.0.0
*/
export function elem(
S: Comparable,
): (a: A) => (m: ReadonlyMap) => boolean {
return (a) => {
const _compare = S.compare(a);
return (ta) => {
for (const b of ta.values()) {
if (_compare(b)) {
return true;
}
}
return false;
};
};
}
/**
* @since 2.0.0
*/
export function entries(
O: Sortable,
): (ta: ReadonlyMap) => ReadonlyArray<[B, A]> {
return (ta) =>
Array.from(ta.entries()).sort(([left], [right]) => O.sort(left, right));
}
/**
* @since 2.0.0
*/
export function keys(O: Sortable): (ta: ReadonlyMap) => K[] {
return (ta) => Array.from(ta.keys()).sort(O.sort);
}
/**
* @since 2.0.0
*/
export function values(O: Sortable): (ta: ReadonlyMap) => A[] {
return (ta) => Array.from(ta.values()).sort(O.sort);
}
/**
* @since 2.0.0
*/
export function fold(
foldr: (accumulator: O, value: A, key: B) => O,
initial: O,
): (ua: ReadonlyMap) => O {
return (ua) => {
let result = initial;
for (const [key, value] of ua.entries()) {
result = foldr(result, value, key);
}
return result;
};
}
/**
* @since 2.0.0
*/
export function collect(
O: Sortable,
): (
fai: (b: B, a: A) => I,
) => (ta: ReadonlyMap) => ReadonlyArray {
const _entries = entries(O);
return (fai) =>
flow(
_entries,
A.map(([b, a]) => fai(b, a)),
);
}
/**
* @since 2.0.0
*/
export function deleteAt(
S: Comparable,
): (key: B) => (map: ReadonlyMap) => ReadonlyMap {
const _lookup = lookupWithKey(S);
return (key) => (map) =>
pipe(
_lookup(key)(map),
O.match(
() => map,
([_key]) => {
const out = new Map(map);
out.delete(_key);
return out;
},
),
);
}
/**
* @since 2.0.0
*/
export function insert(
S: Comparable,
): (value: A) => (key: B) => (ua: ReadonlyMap) => ReadonlyMap {
const _lookup = lookupWithKey(S);
return (value) => (key) => (map) =>
pipe(
_lookup(key)(map),
O.match(
() => {
const _map = new Map(map);
_map.set(key, value);
return _map;
},
([_key, _value]) => {
if (value !== _value) {
const _map = new Map(map);
_map.set(_key, value);
return _map;
}
return map;
},
),
);
}
/**
* @since 2.0.0
*/
export function insertAt(
S: Comparable,
): (key: B) => (value: A) => (ua: ReadonlyMap) => ReadonlyMap {
const _insert = insert(S);
return (key: B) => (value: A) => _insert(value)(key);
}
/**
* @since 2.0.0
*/
export function modify(
S: Comparable,
): (
modifyFn: (a: A) => A,
) => (key: B) => (ta: ReadonlyMap) => ReadonlyMap {
const _lookup = lookupWithKey(S);
return (modifyFn) => (key) => (map) =>
pipe(
_lookup(key)(map),
O.match(
() => map,
([_key, value]) => {
const _map = new Map(map);
_map.set(_key, modifyFn(value));
return _map;
},
),
);
}
/**
* @since 2.0.0
*/
export function modifyAt(
S: Comparable,
): (
key: B,
) => (
modifyFn: (value: A) => A,
) => (map: ReadonlyMap) => ReadonlyMap {
return (key) => (modifyFn) => modify(S)(modifyFn)(key);
}
/**
* @since 2.0.0
*/
export function update(
S: Comparable,
): (value: A) => (key: B) => (map: ReadonlyMap) => ReadonlyMap {
return (value) => (key) => modify(S)(() => value)(key);
}
/**
* @since 2.0.0
*/
export function updateAt(
S: Comparable,
): (key: B) => (value: A) => (map: ReadonlyMap) => ReadonlyMap {
return (key) => (value) => modify(S)(() => value)(key);
}
/**
* @since 2.0.0
*/
export function pop(
S: Comparable,
): (b: B) => (ta: ReadonlyMap) => Option<[A, ReadonlyMap]> {
const _lookup = lookup(S);
const _deleteAt = deleteAt(S);
return (b) => {
const lookupWithB = _lookup(b);
const deleteWithB = _deleteAt(b);
return (ta) =>
pipe(
lookupWithB(ta),
O.map((a) => [a, deleteWithB(ta)]),
);
};
}
/**
* @since 2.0.0
*/
export function isSubmap(
SK: Comparable,
SA: Comparable,
): (second: ReadonlyMap) => (first: ReadonlyMap) => boolean {
const _lookupWithKey = lookupWithKey(SK);
return (second) => {
return (first) => {
for (const [mk, ma] of second.entries()) {
const _compare = SA.compare(ma);
const matches = pipe(
_lookupWithKey(mk)(first),
O.exists(([_, ca]) => _compare(ca)),
);
if (!matches) {
return false;
}
}
return true;
};
};
}
/**
* @since 2.0.0
*/
export function getFlatmappableReadonlyMap(
{ init, combine }: Initializable,
): Flatmappable> {
return {
wrap: (a) => readonlyMap([init(), a]),
map,
apply: (ua) => (ufai) => {
const result = new Map();
for (const [fkey, fai] of ufai) {
for (const [key, a] of ua) {
result.set(combine(fkey)(key), fai(a));
}
}
return result;
},
flatmap: (fuai) => (ua) => {
const result = new Map();
for (const [key, a] of ua) {
const intermediate = fuai(a);
for (const [ikey, i] of intermediate) {
result.set(combine(ikey)(key), i);
}
}
return result;
},
};
}
/**
* @since 2.0.0
*/
export function getComparable(
SK: Comparable,
SA: Comparable,
): Comparable> {
const submap = isSubmap(SK, SA);
return fromCompare((second) => (first) =>
submap(second)(first) && submap(first)(second)
);
}
/**
* @since 2.0.0
*/
export function getCombinable(
SK: Comparable,
SA: Combinable,
): Combinable> {
const lookupKey = lookupWithKey(SK);
return fromCombine((second) => (first) => {
if (isEmpty(first)) {
return second;
}
if (isEmpty(second)) {
return first;
}
const r = new Map(first);
for (const [bk, ba] of second) {
const _combine = SA.combine(ba);
pipe(
lookupKey(bk)(first),
O.match(
() => r.set(bk, ba),
([ak, aa]) => r.set(ak, _combine(aa)),
),
);
}
return r;
});
}
/**
* @since 2.0.0
*/
export function getShowable(
SK: Showable,
SA: Showable,
): Showable> {
return ({
show: (ta) => {
const elements = Array.from(ta).map(([k, a]) =>
`[${SK.show(k)}, ${SA.show(a)}]`
).join(", ");
return `new ReadonlyMap([${elements}])`;
},
});
}
/**
* @since 2.0.0
*/
export const MappableMap: Mappable = { map };
/**
* @since 2.0.0
*/
export const BimappableMap: Bimappable = { map, mapSecond };
/**
* @since 2.0.0
*/
export const FoldableMap: Foldable = { fold };