/** * ReadonlySet is a readonly product structure over objects * and it operates on object equality for deduplication. * * @module ReadonlySet * @since 2.0.0 */ import type { $, Kind, Out } from "./kind.ts"; import type { Applicable } from "./applicable.ts"; import type { Combinable } from "./combinable.ts"; import type { Comparable } from "./comparable.ts"; import type { Either } from "./either.ts"; import type { Filterable } from "./filterable.ts"; import type { Bind, Flatmappable, Tap } from "./flatmappable.ts"; import type { Foldable } from "./foldable.ts"; import type { Initializable } from "./initializable.ts"; import type { BindTo, Mappable } from "./mappable.ts"; import type { Option } from "./option.ts"; import type { Pair } from "./pair.ts"; import type { Predicate } from "./predicate.ts"; import type { Refinement } from "./refinement.ts"; import type { Showable } from "./showable.ts"; import type { Traversable } from "./traversable.ts"; import type { Wrappable } from "./wrappable.ts"; import { flow, identity, pipe } from "./fn.ts"; import { fromCompare } from "./comparable.ts"; import { fromCombine } from "./combinable.ts"; import { createBind, createTap } from "./flatmappable.ts"; import { createBindTo } from "./mappable.ts"; import * as O from "./option.ts"; import * as E from "./either.ts"; /** * Extract the inner type of a ReadonlySet * * @since 2.0.0 */ export type TypeOf = T extends ReadonlySet ? A : never; /** * Specifies ReadonlySet as a Higher Kinded Type, with covariant * parameter A corresponding to the 0th index of any substitutions. * * @since 2.0.0 */ export interface KindReadonlySet extends Kind { readonly kind: ReadonlySet>; } /** * Constructs a new ReadonlySet over type A that conuains no values. * * @example * ```ts * import * as S from "./set.ts"; * * const result = S.init(); // ReadonlySet with no members. * ``` * * @since 2.0.0 */ export function init(): ReadonlySet { return new Set(); } /** * Constructs a new ReadonlySet from an arbitrary number * of values. * * @example * ```ts * import * as S from "./set.ts"; * * const result = S.set(1, 2, 3); // ReadonlySet * ``` * * @since 2.0.0 */ export function set(...as: readonly [A, ...A[]]): ReadonlySet { return new Set(as); } /** * Copies an existing ReadonlySet into a new ReadonlySet, keeping * references to the original members. * * @example * ```ts * import * as S from "./set.ts"; * * const original = S.set(1, 2, 3); * const copy = S.copy(original); * * const result1 = original === copy; // false * * const has = (value: number) => original.has(value) === copy.has(value); * * const result2 = has(1); // true; * const result3 = has(10); // true; * ``` * * @since 2.0.0 */ export function copy(ua: ReadonlySet): ReadonlySet { return new Set(ua); } /** * Operates like Array.some, testing values in a ReadonlySet with a Predicate * until either the predicate returns true for a value or all of the * values have been tested. Shortcircuits on the first value that * returns true. This is the dual of every. * * @example * ```ts * import * as S from "./set.ts"; * * const some = S.some((n: number) => n > 0); * * const result1 = some(S.set(1, 2, 3)); // true * const result2 = some(S.set(0)); // false * const result3 = some(S.init()); // false * const result4 = some(S.set(-1, -2, -3, 1)); // true * ``` * * @since 2.0.0 */ export function some( predicate: Predicate, ): (ua: ReadonlySet) => boolean { return (ua) => { for (const a of ua) { if (predicate(a)) { return true; } } return false; }; } /** * Operates like Array.every, testing values in a ReadonlySet with a Predicate * until either the predicate returns false for a value or all of the * values have been tested as true. Shortcircuits on the first value that * returns false. This is the dual of some. * * @example * ```ts * import * as S from "./set.ts"; * * const some = S.some((n: number) => n > 0); * * const result1 = some(S.set(1, 2, 3)); // true * const result2 = some(S.set(0)); // false * const result3 = some(S.init()); // false * const result4 = some(S.set(-1, -2, -3, 1)); // true * ``` * * @since 2.0.0 */ export function every( predicate: Predicate, ): (ua: ReadonlySet) => boolean { return (ua) => { for (const a of ua) { if (!predicate(a)) { return false; } } return true; }; } /** * Given an insuance of Comparable create a function * that takes a value A and returns a predicate over * ReadonlySet the returns true if there are any * members of the set that are equal to the value. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * * const elem = S.elem(N.ComparableNumber); * * const set = S.set(1, 2, 3); * * const result1 = pipe(set, elem(1)); // true * const result2 = pipe(set, elem(10)); // false * ``` * * @since 2.0.0 */ export function elem( { compare }: Comparable, ): (value: A) => (ua: ReadonlySet) => boolean { return (a) => some(compare(a)); } /** * Given an instance of Comparable create a function * that uakes a ReadonlySet and returns a predicate over * a value A the returns true if the value is a member * of the set. This is like elem but with the set and value * parameters swapped. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * * const elemOf = S.elemOf(N.ComparableNumber); * * const set = S.set(1, 2, 3); * const inSet = elemOf(set); * * const result1 = inSet(1); // true * const result2 = inSet(10); // false * ``` * * @since 2.0.0 */ export function elemOf( S: Comparable, ): (ua: ReadonlySet) => (a: A) => boolean { const _elem = elem(S); return (ua) => (a) => _elem(a)(ua); } /** * Given an instance of Comparable return a function * `second => first => boolean` that returns true when * every member of first is in second. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * * const subset = S.isSubset(N.ComparableNumber); * * const big = S.set(1, 2, 3, 4, 5); * const small = S.set(2, 4); * * const result1 = pipe(big, subset(small)); // false * const result2 = pipe(small, subset(big)); // true; * ``` * * @since 2.0.0 */ export function isSubset( S: Comparable, ): (second: ReadonlySet) => (first: ReadonlySet) => boolean { return flow(elemOf(S), every); } /** * Given an instance of Comparable return a function that takes * two ReadonlySets and merges them into a new ReadonlySet * that contains all the elements from both sets. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * * const union = S.union(N.ComparableNumber); * const s1 = S.set(1, 2, 3, 4); * const s2 = S.set(3, 4, 5, 6); * * const result = pipe(s1, union(s2)); * // Set(1, 2, 3, 4, 5, 6) * ``` * * @since 2.0.0 */ export function union( S: Comparable, ): (second: ReadonlySet) => (first: ReadonlySet) => ReadonlySet { return (second) => (first) => { const out = copy(first) as Set; const isIn = elemOf(S)(out); for (const b of second) { if (!isIn(b)) { out.add(b); } } return out; }; } /** * Given an instance of Comparable return a function that takes * two ReadonlySets and returns a new set with only * the elements that exist in both sets. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * * const intersect = S.intersection(N.ComparableNumber); * const s1 = S.set(1, 2, 3, 4); * const s2 = S.set(3, 4, 5, 6); * * const result = pipe(s1, intersect(s2)); * // Set(3, 4) * ``` * * @since 2.0.0 */ export function intersection( S: Comparable, ): (ua: ReadonlySet) => (tb: ReadonlySet) => ReadonlySet { return (ua) => { const isIn = elemOf(S)(ua); return (tb) => { const out = new Set(); for (const b of tb) { if (isIn(b)) { out.add(b); } } return out; }; }; } /** * Given an instance of Comparable create a function that will * take a ReadonlySet and return a new ReadonlySet where * any members that are equal are deduplicated. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import * as E from "./comparable.ts"; * * const eq = E.struct({ num: N.ComparableNumber }); * const compact = S.compact(eq); * * const set = S.set({ num: 1 }, { num: 1 }, { num: 2 }); * // Set({ num: 1 }, { num: 1 }, { num: 2 }) * * const result = compact(set); // Set({ num: 1 }, { num: 2 }) * ``` * * @since 2.0.0 */ export function compact( S: Comparable, ): (ua: ReadonlySet) => ReadonlySet { return (ua) => { const out = new Set(); const isIn = elemOf(S)(out); for (const a of ua) { if (!isIn(a)) { out.add(a); } } return out; }; } /** * Given a value A create a new ReadonlySet that * contains that value. * * @example * ```ts * import * as S from "./set.ts"; * * const result = S.wrap(1); // Set(1); * ``` * * @since 2.0.0 */ export function wrap(a: A): ReadonlySet { return set(a); } /** * Given a function A -> I and a ReadonlySet return * a new ReadonlySet where the values were created * by passing each A through the A -> I function. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set("hello", "world", "goodbye"); * * const result = pipe( * set, * S.map(s => s.length), * ); // Set(5, 7); * ``` * * @since 2.0.0 */ export function map( fai: (a: A) => I, ): (ua: ReadonlySet) => ReadonlySet { return (ua) => { const ti = new Set(); for (const a of ua) { ti.add(fai(a)); } return ti; }; } /** * Given a ReadonlySet of functions A -> I and * a ReadonlySet return a ReadonlySet by applying * every function to every value A. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * type Person = { name: string, age: number }; * * const person = (name: string) => (age: number): Person => ({ name, age }); * * const result = pipe( * S.wrap(person), * S.apply(S.wrap("Brandon")), * S.apply(S.wrap(37)), * ); // ReadonlySet * ``` * * @since 2.0.0 */ export function apply( ua: ReadonlySet, ): (ufai: ReadonlySet<(a: A) => I>) => ReadonlySet { return (ufai: ReadonlySet<(a: A) => I>): ReadonlySet => { const ti = new Set(); for (const a of ua) { for (const fai of ufai) { ti.add(fai(a)); } } return ti; }; } /** * Given a function A -> ReadonlySet and a ReadonlySet * return a ReadonlySet created by applying the function * to every value A and joining all the resulting ReadonlySets. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set(1, 2, 3, 4, 5); * * const result = pipe( * set, * S.flatmap(n => S.set(n, n + 1, n + 2)), * ); // Set(1, 2, 3, 4, 5, 6, 7); * ``` * * @since 2.0.0 */ export function flatmap( fati: (a: A) => ReadonlySet, ): (ua: ReadonlySet) => ReadonlySet { return (ua) => { const ti = new Set(); for (const a of ua) { const _ti = fati(a); for (const i of _ti) { ti.add(i); } } return ti; }; } /** * Given a ReadonlySet of ReadonlySet, flatten all of the inner * sets and return a ReadonlySet. * * @example * ```ts * import * as S from "./set.ts"; * * const setOfSets = S.set(S.set(1, 2), S.set(3, 4), S.set(1, 4)); * * const result = S.join(setOfSets); // Set(1, 2, 3, 4) * ``` * * @since 2.0.0 */ export function join(uua: ReadonlySet>): ReadonlySet { return pipe(uua, flatmap(identity)); } /** * Given a Refinement or Predicate over A and a ReadonlySet return * a new ReadonlySet with only values for which the predicate or * refinement return true. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set(1, 2, 3, 4, 5); * * const result1 = pipe(set, S.filter(n => n > 2)); // Set(3, 4, 5) * const result2 = pipe(set, S.filter(n => n === 0)); // Set() * ``` * * @since 2.0.0 */ export function filter( refinement: Refinement, ): (ua: ReadonlySet) => ReadonlySet; export function filter( predicate: Predicate, ): (ua: ReadonlySet) => ReadonlySet; export function filter( predicate: Predicate, ): (ua: ReadonlySet) => ReadonlySet { return (ua) => { const _ua = new Set(); for (const a of ua) { if (predicate(a)) { _ua.add(a); } } return _ua; }; } /** * Given a function A -> Option and a ReadonlySet return * a ReadonlySet by applying the function to all values A. Any * Nones will not enter the resultant set while Some values will. * This is effectively filtering and mapping simultaneously. * * @example * ```ts * import * as S from "./set.ts"; * import * as O from "./option.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set("one", "two", "three", "four", "five"); * * const result = pipe( * set, * S.filterMap(s => s.includes('o') ? O.some(s.length) : O.none), * ); // Set(3, 4) * ``` * * @since 2.0.0 */ export function filterMap( fai: (a: A) => Option, ): (ua: ReadonlySet) => ReadonlySet { return (ua) => { const output = new Set(); for (const a of ua) { const value = fai(a); if (O.isSome(value)) { output.add(value.value); } } return output; }; } /** * Given a Predicate or Refinement over A and a ReadonlySet * return a Pair with a first value being a ReadonlySet of values * that return true when applied to the refinement or predicate * and a second value being a ReadonlySet of values that return * false when applied to the predicate. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set(1, 2, 3, 4); * * const result = pipe( * set, * S.partition(n => n > 2), * ); // [Set(3, 4), Set(1, 2)] * ``` * * @since 2.0.0 */ export function partition( refinement: Refinement, ): (ua: ReadonlySet) => Pair, ReadonlySet>; export function partition( predicate: Predicate, ): (ua: ReadonlySet) => Pair, ReadonlySet>; export function partition( predicate: Predicate, ): (ua: ReadonlySet) => Pair, ReadonlySet> { return (ua) => { const first = new Set(); const second = new Set(); for (const a of ua) { if (predicate(a)) { first.add(a); } else { second.add(a); } } return [first, second]; }; } /** * Given a function A -> Either and a ReadonlySet return * a Pair(ReadonlySet, ReadonlySet) by applying every value A * in the set to the partitioning function. * * @example * ```ts * import * as S from "./set.ts"; * import * as E from "./either.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set("one", "two", "three", "four"); * * const result = pipe( * set, * S.partitionMap(s => s.includes('o') ? E.right(s) : E.left(s.length)), * ); // [Set("one", "two", "four"), Set(5)] * ``` * * @since 2.0.0 */ export function partitionMap( fai: (a: A) => Either, ): (ua: ReadonlySet) => Pair, ReadonlySet> { return (ua) => { const first = new Set(); const second = new Set(); for (const a of ua) { const result = fai(a); if (E.isRight(result)) { first.add(result.right); } else { second.add(result.left); } } return [first, second]; }; } /** * Reduce a ReadonlySet to a value O by iterating over * the values of the set and collecting them with the reducing function. * * @example * ```ts * import * as S from "./set.ts"; * import { pipe } from "./fn.ts"; * * const set = S.set(1, 2, 3, 4); * * const result = pipe( * set, * S.fold((previous, current) => previous + current, 0), * ); // 10 * ``` * * @since 2.0.0 */ export function fold( foao: (o: O, a: A) => O, o: O, ): (ua: ReadonlySet) => O { return (ua) => { let out = o; for (const a of ua) { out = foao(out, a); } return out; }; } // This is an unsafe Add for ReadonlySet that violates Readonly contract const unsafeAdd = (ua: ReadonlySet) => (a: A): Set => { (ua as Set).add(a); return ua as Set; }; /** * Traverse a ReadonlySet value by value, applying a function * A -> V, then collecting all of the I values into ReadonlySet * and returning V>. In concrete terms this can take * ReadonlySet> and turn it into Option> and * other ADT inversions. * * @example * ```ts * import * as S from "./set.ts"; * import * as O from "./option.ts"; * import { pipe } from "./fn.ts"; * * const traverseOption = S.traverse(O.ApplicableOption); * const invert = traverseOption((o: O.Option) => o); * * const result1 = pipe( * S.set(O.some(1), O.some(2), O.some(3)), * invert, * ); // Some(Set(1, 2, 3)) * const result2 = pipe( * S.set(O.some(1), O.some(2), O.none), * invert, * ); // None * ``` * * @since 2.0.0 */ export function traverse( A: Applicable, ): ( favi: (a: A) => $, ) => (ua: ReadonlySet) => $, J, K], [L], [M]> { return ( favi: (a: A) => $, ): (ua: ReadonlySet) => $, J, K], [L], [M]> => fold( (vis, a) => pipe(vis, A.map(unsafeAdd), A.apply(favi(a))), A.wrap(init() as Set), ); } /** * Given an instance of Showable return an instance of Showable>. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * * const { show } = S.getShowableSet(N.ShowableNumber); * * const result1 = show(S.init()); // "Set([])" * const result2 = show(S.set(1, 2, 3)); // "Set([1, 2, 3])" * ``` * * @since 2.0.0 */ export function getShowableSet(S: Showable): Showable> { return ({ show: (s) => `Set([${Array.from(s.values()).map(S.show).join(", ")}])`, }); } /** * Given an instance of Comparable return Comparable>. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import { pipe } from "./fn.ts"; * * const { compare } = S.getComparableSet(N.ComparableNumber); * * const result1 = compare( * S.set(1, 2, 3))( * S.set(3, 2, 1) * ); // true * const result2 = compare( * S.set(1, 2, 3))( * S.set(1, 2, 3, 4) * ); // false * ``` * * @since 2.0.0 */ export function getComparableSet( S: Comparable, ): Comparable> { const subset = isSubset(S); return fromCompare((second) => (first) => subset(first)(second) && subset(second)(first) ); } /** * Given an instance of Comparable create a Combinable> where * combine creates a union of two ReadonlySets. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import * as M from "./combinable.ts"; * import { pipe } from "./fn.ts"; * * const monoid = S.getCombinableSet(N.ComparableNumber); * const combineAll = M.getCombineAll(monoid); * * const result1 = combineAll( * S.set(1, 2, 3), * S.set(4, 5, 6), * S.set(1, 3, 5, 7) * ); // Set(1, 2, 3, 4, 5, 6, 7) * const result3 = combineAll(S.init()); // Set() * ``` * * @since 2.0.0 */ export function getCombinableSet( S: Comparable, ): Combinable> { return fromCombine(union(S)); } /** * Given an instance of Comparable create a Combinable> where * combine creates a union of two ReadonlySets. * * @example * ```ts * import * as S from "./set.ts"; * import * as N from "./number.ts"; * import * as M from "./combinable.ts"; * import { pipe } from "./fn.ts"; * * const monoid = S.getInitializableSet(N.ComparableNumber); * const combineAll = M.getCombineAll(monoid); * * const result1 = combineAll( * S.set(1, 2, 3), * S.set(4, 5, 6), * S.set(1, 3, 5, 7) * ); // Set(1, 2, 3, 4, 5, 6, 7) * const result3 = combineAll(S.init()); // Set() * ``` * * @since 2.0.0 */ export function getInitializableSet( C: Comparable, ): Initializable> { return { init: () => new Set(), ...getCombinableSet(C), }; } /** * The canonical implementation of Applicable for ReadonlySet. It contains * the methods wrap, ap, and map. * * @since 2.0.0 */ export const ApplicableSet: Applicable = { apply, map, wrap }; /** * The canonical implementation of Filterable for ReadonlySet. It contains * the methods filter, filterMap, partition, and partitionMap. * * @since 2.0.0 */ export const FilterableSet: Filterable = { filter, filterMap, partition, partitionMap, }; /** * The canonical implementation of Flatmappable for ReadonlySet. It contains * the methods wrap, ap, map, join, and flatmap. * * @since 2.0.0 */ export const FlatmappableSet: Flatmappable = { apply, flatmap, map, wrap, }; /** * The canonical implementation of Foldable for ReadonlySet. It contains * the method fold. * * @since 2.0.0 */ export const FoldableSet: Foldable = { fold }; /** * The canonical implementation of Mappable for ReadonlySet. It contains * the method map. * * @since 2.0.0 */ export const MappableSet: Mappable = { map }; /** * The canonical implementation of Traversable for ReadonlySet. It contains * the methods map, fold, and traverse. * * @since 2.0.0 */ export const TraversableSet: Traversable = { map, fold, traverse, }; /** * @since 2.0.0 */ export const WrappableSet: Wrappable = { wrap }; /** * @since 2.0.0 */ export const tap: Tap = createTap(FlatmappableSet); /** * @since 2.0.0 */ export const bind: Bind = createBind(FlatmappableSet); /** * @since 2.0.0 */ export const bindTo: BindTo = createBindTo(MappableSet);