// ets_tracing: off /* adapted from https://github.com/gcanti/fp-ts */ /** * Multi-way trees (aka rose trees) and forests, where a forest is * * ```ts * type Forest = Array> * ``` */ import * as A from "../Collections/Immutable/Array/index.js" import type { Equal } from "../Equal/index.js" import { makeEqual } from "../Equal/index.js" import { pipe } from "../Function/index.js" import type { Identity } from "../Identity/index.js" import * as IO from "../IO/index.js" import type { TreeURI } from "../Modules/index.js" import * as DSL from "../Prelude/DSL/index.js" import { getApplicativeF } from "../Prelude/DSL/index.js" import type { URI } from "../Prelude/index.js" import * as P from "../Prelude/index.js" import { sequenceF } from "../Prelude/index.js" import type { Show } from "../Show/index.js" export declare type Forest = ReadonlyArray> export interface Tree { readonly value: A readonly forest: Forest } export function make(value: A, forest: Forest = A.empty()): Tree { return { value, forest } } export function getShow(S: Show): Show> { function showSafe(t: Tree): IO.IO { if (t.forest === A.empty() || t.forest.length === 0) { return IO.succeed(`make(${S.show(t.value)})`) } return pipe( t.forest, IO.forEachArray(showSafe), IO.map((forest) => `make(${S.show(t.value)}, [${forest.join(", ")}])`) ) } return { show: (x) => IO.run(showSafe(x)) } } export function getEqual(E: Equal): Equal> { function equalsForestSafe(x: Forest, y: Forest, i = 0): IO.IO { if (i === x.length) { return IO.succeed(true) } return pipe( IO.suspend(() => equalsSafe(x[i]!, y[i]!)), IO.chain((b) => (b ? equalsForestSafe(x, y, i + 1) : IO.succeed(false))) ) } function equalsSafe(x: Tree, y: Tree): IO.IO { return !E.equals(x.value, y.value) ? IO.succeed(false) : x.forest.length !== y.forest.length ? IO.succeed(false) : equalsForestSafe(x.forest, y.forest) } return makeEqual((x, y) => IO.run(equalsSafe(x, y))) } function draw(indentation: string, forest: Forest): IO.IO { return IO.gen(function* (_) { let r = "" const len = forest.length let tree: Tree for (let i = 0; i < len; i++) { tree = forest[i]! const isLast = i === len - 1 r += indentation + (isLast ? "└" : "├") + "─ " + tree.value r += yield* _( draw(indentation + (len > 1 && !isLast ? "│ " : " "), tree.forest) ) } return r }) } /** * Neat 2-dimensional drawing of a forest */ export function drawForest(forest: Forest): string { return IO.run(draw("\n", forest)) } /** * Neat 2-dimensional drawing of a tree */ export function drawTree(tree: Tree): string { return tree.value + drawForest(tree.forest) } /** * Build a tree from a seed value */ export function unfoldTree(b: B, f: (b: B) => [A, Array]): Tree { return IO.run(unfoldTreeSafe(b, f)) } /** * Build a tree from a seed value */ export function unfoldTreeSafe(b: B, f: (b: B) => [A, Array]): IO.IO> { const [a, bs] = f(b) return pipe( IO.suspend(() => unfoldForestSafe(bs, f)), IO.map((forest) => ({ value: a, forest })) ) } /** * Build a tree from a seed value */ export function unfoldForest( bs: Array, f: (b: B) => [A, Array] ): Forest { return IO.run(unfoldForestSafe(bs, f)) } /** * Build a tree from a seed value */ export function unfoldForestSafe( bs: Array, f: (b: B) => [A, Array] ): IO.IO> { return pipe( bs, IO.forEachArray((b) => unfoldTreeSafe(b, f)) ) } /** * Monadic tree builder, in depth-first order */ export function unfoldTreeM( M: P.Monad & P.Applicative ): ( b: B, f: (b: B) => P.Kind]> ) => P.Kind> export function unfoldTreeM( M: P.Monad> & P.Applicative> ): (b: B, f: (b: B) => P.HKT]>) => P.HKT> { const unfoldForestMM = unfoldForestM(M) const chain = DSL.chainF(M) const succeed = DSL.succeedF(M) return (b, f) => pipe( f(b), chain(([a, bs]) => pipe( unfoldForestMM(bs, f), chain((ts) => succeed({ value: a, forest: ts })) ) ) ) } /** * Monadic forest builder, in depth-first order */ export function unfoldForestM( M: P.Monad & P.Applicative ): ( bs: Array, f: (b: B) => P.Kind]> ) => P.Kind> export function unfoldForestM( M: P.Monad> & P.Applicative> ): (bs: Array, f: (b: B) => P.HKT]>) => P.HKT> { const traverseM = A.forEachF(M) return (bs, f) => pipe( bs, traverseM((b) => unfoldTreeM(M)(b, f)) ) } export function elem_(E: Equal): (fa: Tree, a: A) => boolean { function goForest(forest: Forest, a: A, i = 0): IO.IO { if (i === forest.length) { return IO.succeed(false) } return pipe( IO.suspend(() => go(forest[i]!, a)), IO.chain((b) => (b ? IO.succeed(true) : goForest(forest, a, i + 1))) ) } function go(fa: Tree, a: A): IO.IO { if (E.equals(a, fa.value)) { return IO.succeed(true) } return IO.suspend(() => goForest(fa.forest, a)) } return (fa, a) => IO.run(go(fa, a)) } export function elem(E: Equal): (a: A) => (fa: Tree) => boolean { const el = elem_(E) return (a) => (fa) => el(fa, a) } /** * Fold a tree into a "summary" value in depth-first order. * * For each node in the tree, apply `f` to the `value` and the result of applying `f` to each `forest`. * * This is also known as the catamorphism on trees. */ export function fold(f: (a: A, bs: readonly B[]) => B): (tree: Tree) => B { function go(tree: Tree): IO.IO { return pipe( tree.forest, IO.forEachArray(go), IO.map((bs) => f(tree.value, bs)) ) } return (tree) => IO.run(go(tree)) } export function map_(fa: Tree, f: (a: A) => B): Tree { function go(node: Tree): IO.IO> { return pipe( node.forest, IO.forEachArray(go), IO.map((forest) => ({ value: f(node.value), forest })) ) } return IO.run(go(fa)) } export function of(a: A): Tree { return { value: a, forest: A.empty() } } export function ap_(fab: Tree<(a: A) => B>, fa: Tree): Tree { return chain_(fab, (f) => map_(fa, f)) } export function chain_(fa: Tree, f: (a: A) => Tree): Tree { function go(node: Tree): IO.IO> { const { forest, value } = f(node.value) return pipe( node.forest, IO.forEachArray(go), IO.map((x) => ({ value, forest: [...forest, ...x] })) ) } return IO.run(go(fa)) } export function reduce_(fa: Tree, b: B, f: (b: B, a: A) => B): B { function go(node: Tree, b: B): IO.IO { return IO.gen(function* (_) { let r: B = f(b, node.value) const len = fa.forest.length for (let i = 0; i < len; i++) { r = yield* _(go(node.forest[i]!, r)) } return r }) } return IO.run(go(fa, b)) } export function foldMap_(M: Identity) { return (fa: Tree, f: (a: A) => M): M => reduce_(fa, M.identity, (acc, a) => M.combine(acc, f(a))) } export function reduceRight_(fa: Tree, b: B, f: (a: A, b: B) => B): B { function go(node: Tree, b: B): IO.IO { return IO.gen(function* (_) { let r: B = b const len = node.forest.length for (let i = len - 1; i >= 0; i--) { r = yield* _(go(node.forest[i]!, r)) } return f(node.value, r) }) } return IO.run(go(fa, b)) } export const forEachF = P.implementForEachF<[URI]>()((_) => (G) => { const traverseF = A.forEachF(G) const r = (f: (a: A) => P.HKT) => (ta: Tree): P.HKT> => pipe( f(ta.value), G.map((value: B) => (forest: Forest) => ({ value, forest })), DSL.apF(G)( pipe( ta.forest, traverseF((t) => r(f)(t)) ) ) ) return r }) export const ForEach = P.instance]>>({ forEachF, map }) export const sequence = sequenceF(ForEach) export function extract(wa: Tree): A { return wa.value } export function extend_(wa: Tree, f: (wa: Tree) => B): Tree { function go(node: Tree): IO.IO> { return pipe( node.forest, IO.forEachArray(go), IO.map((forest) => ({ value: f(node), forest })) ) } return IO.run(go(wa)) } export function extend(f: (fa: Tree) => B) { return (ma: Tree): Tree => extend_(ma, f) } export function ap(fa: Tree) { return (fab: Tree<(a: A) => B>): Tree => ap_(fab, fa) } export function apFirst(fb: Tree) { return (fa: Tree): Tree => ap_( map_(fa, (a) => () => a), fb ) } export function apFirst_(fa: Tree, fb: Tree): Tree { return ap_( map_(fa, (a) => () => a), fb ) } export function apSecond(fb: Tree) { return (fa: Tree): Tree => ap_( map_(fa, () => (b: B) => b), fb ) } export function apSecond_(fa: Tree, fb: Tree): Tree { return ap_( map_(fa, () => (b: B) => b), fb ) } export function chain(f: (a: A) => Tree) { return (ma: Tree): Tree => chain_(ma, f) } export function tap(f: (a: A) => Tree) { return (ma: Tree): Tree => chain_(ma, (x) => map_(f(x), () => x)) } export function tap_(ma: Tree, f: (a: A) => Tree): Tree { return chain_(ma, (x) => map_(f(x), () => x)) } export function duplicate(ma: Tree): Tree> { return extend_(ma, (x) => x) } export function flatten(mma: Tree>): Tree { return chain_(mma, (x) => x) } export function foldMap(M: Identity) { return (f: (a: A) => M) => (fa: Tree): M => foldMap_(M)(fa, f) } export function map(f: (a: A) => B) { return (fa: Tree): Tree => map_(fa, f) } export function reduce(b: B, f: (b: B, a: A) => B) { return (fa: Tree): B => reduce_(fa, b, f) } export function reduceRight(b: B, f: (a: A, b: B) => B) { return (fa: Tree): B => reduceRight_(fa, b, f) } export const Foldable = P.instance]>>({ foldMap, reduce, reduceRight }) export const Monad = P.instance]>>({ any: () => of({}), flatten, map }) export const Applicative = getApplicativeF(Monad) export const gen = DSL.genF(Monad) export const bind = DSL.bindF(Monad) const do_ = DSL.doF(Monad) export { do_ as do } export { branch as if, branch_ as if_ } export const struct = DSL.structF(Applicative) export const tuple = DSL.tupleF(Applicative) /** * Matchers */ export const { match, matchIn, matchMorph, matchTag, matchTagIn } = DSL.matchers(Monad) /** * Conditionals */ const branch = DSL.conditionalF(Monad) const branch_ = DSL.conditionalF_(Monad)