import { Algebra, Foldable, Monad, Traversable } from './interfaces.js' import { curry } from '../..//vendor/izuna/src/index.js' import { Reducer } from './utilities.js' /** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Foldable as FoldableT, Functor, Morphism, Traversable as TraversableT } from './types.js' */ const kleisli = curry((f, g, x) => f(x).chain(g)) /** * @template T * @typedef {Pure | Impure} Free */ /** @template T */ export class Pure extends Algebra(Monad, Traversable) { #value /** * @param {T} value */ constructor(value) { super() this.#value = value } /** * @type {Chain['chain']} */ chain(f) { return f(this.#value) } /** * @type {Functor['map']} */ map(f) { return pure(f(this.#value)) } /** * @template U * @type {Apply['ap']} * @this {Pure>} * @param {Pure} b * @returns {Free} */ ap(b) { return /** @type {Free} */ (b.map(this.#value)) } /** * @type {FoldableT['reduce']} */ reduce(f, acc) { return f(acc, this.#value) } /** * @template U * @template {Applicative} M * @template {ApplicativeTypeRef} TypeRef * @param {TypeRef} _A * @param {(value: T) => M} f * @returns {Applicative>} */ traverse(_A, f) { return /** @type {Applicative>} */ (f(this.#value).map(pure)) } run() { return this.#value } } /** * @template T, [N=any] * @implements Functor * @implements Chain * @implements TraversableT */ export class Impure extends Algebra(Monad, Foldable, Traversable) { #value #next /** * @param {T} value * @param {(value: T) => Free} f */ constructor(value, f) { super() this.#value = value this.#next = f } /** * @type {Chain['chain']} * @template U * @param {(value: T) => Free} f * @returns {Free} */ chain(f) { return impure(this.#value, kleisli(this.#next, f)) } /** * @template {Functor} U * @type {Functor['map']} * @param {Morphism} f * @returns {Free} */ map(f) { return impure( this.#value, y => /** @type {Free} */(f(y).map(f)) ) } /** * @template U * @type {Apply['ap']} * @this {Free>} * @param {Free} b * @returns {Free} */ ap(b) { return /** @type {Free} */ (this.chain(f => /** @type {Free} */(b.map(f)) )) } /** * @type {FoldableT['reduce']} */ reduce(f, acc) { const Const = Reducer(f, acc) // @ts-ignore return this.traverse(Const, x => new Const(x)).value } /** * @type {TraversableT['traverse']} * @template U * @template {Applicative} M * @template {ApplicativeTypeRef} TypeRef * @this {Impure} * @param {TypeRef} A * @param {(value: T | N) => M} f * @returns {Applicative>} */ traverse(A, f) { const next = this.#next(this.#value) if (next instanceof Pure) { return next.traverse(A, f) } else { const fb = f(this.#value) const rest = next.traverse(A, f) return /** @type {Applicative>} */ (rest.ap( /** @type {Apply, U>>} */(fb.map(b => next => impure(b, () => /** @type {Free} */(next) ))) )) } } run() { return this.#next(this.#value).run() } } /** * @template T * @param {T} x * @returns {Pure} */ const pure = x => new Pure(x) /** * @template T * @param {T} x * @param {(value: T) => Free} f * @returns {Impure} */ const impure = (x, f) => new Impure(x, f) /** * @template T * @param {T} x * @returns Impure */ export const liftF = x => impure(x, pure) const TypeRef = () => { } TypeRef.constructor.of = pure export const Free = TypeRef