import { Algebra, Monad } from './interfaces.js' /** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Functor, Morphism } from './types.js' */ const Interfaces = Algebra(Monad) /** * @template T * @typedef {Applicative & (Pure | Impure)} Free */ /** * @template T * @implements {Applicative} */ class Pure extends Interfaces { #value /** * @param {T} value */ constructor(value) { super() this.#value = value } /** * @template T * @param {T} value */ static of(value) { return liftF(value) } /** * @template U * @type {Chain['chain']} * @param {Morphism>} f * @returns {Pure} */ chain(f) { return f(this.#value) } /** * @type {Functor['map']} */ map(f) { return this.chain(x => pure(f(x))) } /** * @template U * @type {Apply['ap']} * @param {Free>} b * @returns {Free} */ ap(b) { return /** @type {Free} */ (b.chain(f => /** @type {Chain} */(this.map(f)) )) } interpret() { return this.#value } toString() { return `Pure(${this.#value})` } } /** * @template T * @implements {Applicative} */ class Impure extends Interfaces { #value #next /** * @param {T} value * @param {(value: T) => Free} next */ constructor(value, next) { super() this.#value = value this.#next = next } /** * @template T * @param {T} value */ static of(value) { return liftF(value) } /** * @template U * @type {Chain['chain']} * @param {Morphism>} f * @returns {Free} */ chain(f) { return /** @type {Free} */ (impure( this.#value, x => /** @type {Free} */(this.#next(x).chain(f)) )) } /** * @template U * @type {Functor['map']} * @param {Morphism} f * @returns {Free} */ map(f) { return /** @type {Free} */ (impure( this.#value, x => /** @type Free} */(this.#next(x).map(f)) )) } /** * @template U * @type {Apply['ap']} * @param {Free>} b * @returns {Free} */ ap(b) { return /** @type {Free} */ (impure( this.#value, x => /** @type {Free} */(b.chain(f => /** @type {Free} */(this.#next(x).map(f))) ) )) } interpret() { return this.#next(this.#value).interpret() } toString() { return `Impure(${this.#value}, ${this.#next})` } } /** * @template T * @param {T} value */ export const pure = value => new Pure(value) /** * @template T * @param {T} x * @param {(value: T) => Free} f */ export const impure = (x, f) => new Impure(x, f) /** * @template T * @param {T} value */ export const liftF = value => impure(value, pure) /** * @template T * @type {ApplicativeTypeRef>} */ export const Free = Pure