kojima/src/algebra/free.js
2025-04-13 04:31:29 -05:00

175 lines
2.8 KiB
JavaScript

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