kojima/src/algebra/free.js

198 lines
3.8 KiB
JavaScript

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<T> | Impure<T>} Free
*/
/** @template T */
export class Pure extends Algebra(Monad, Traversable) {
#value
/**
* @param {T} value
*/
constructor(value) {
super()
this.#value = value
}
/**
* @type {Chain<T>['chain']}
*/
chain(f) {
return f(this.#value)
}
/**
* @type {Functor<T>['map']}
*/
map(f) {
return pure(f(this.#value))
}
/**
* @template U
* @type {Apply<T>['ap']}
* @this {Pure<Morphism<U, T>>}
* @param {Pure<U>} b
* @returns {Free<T>}
*/
ap(b) {
return /** @type {Free<T>} */ (b.map(this.#value))
}
/**
* @type {FoldableT<T>['reduce']}
*/
reduce(f, acc) {
return f(acc, this.#value)
}
/**
* @template U
* @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} TypeRef
* @param {TypeRef} _A
* @param {(value: T) => M} f
* @returns {Applicative<Free<U>>}
*/
traverse(_A, f) {
return /** @type {Applicative<Free<U>>} */ (f(this.#value).map(pure))
}
run() {
return this.#value
}
}
/**
* @template T, [N=any]
* @implements Functor<T>
* @implements Chain<T>
* @implements TraversableT<T>
*/
export class Impure extends Algebra(Monad, Foldable, Traversable) {
#value
#next
/**
* @param {T} value
* @param {(value: T) => Free<N>} f
*/
constructor(value, f) {
super()
this.#value = value
this.#next = f
}
/**
* @type {Chain<T>['chain']}
* @template U
* @param {(value: T) => Free<U>} f
* @returns {Free<T>}
*/
chain(f) {
return impure(this.#value, kleisli(this.#next, f))
}
/**
* @template {Functor<T>} U
* @type {Functor<T>['map']}
* @param {Morphism<T, U>} f
* @returns {Free<T>}
*/
map(f) {
return impure(
this.#value,
y => /** @type {Free<U>} */(f(y).map(f))
)
}
/**
* @template U
* @type {Apply<T>['ap']}
* @this {Free<Morphism<U, T>>}
* @param {Free<U>} b
* @returns {Free<T>}
*/
ap(b) {
return /** @type {Free<T>} */ (this.chain(f =>
/** @type {Free<T>} */(b.map(f))
))
}
/**
* @type {FoldableT<T>['reduce']}
*/
reduce(f, acc) {
const Const = Reducer(f, acc)
// @ts-ignore
return this.traverse(Const, x => new Const(x)).value
}
/**
* @type {TraversableT<T>['traverse']}
* @template U
* @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} TypeRef
* @this {Impure<T, N>}
* @param {TypeRef} A
* @param {(value: T | N) => M} f
* @returns {Applicative<Free<U>>}
*/
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<Free<U>>} */ (rest.ap(
/** @type {Apply<Morphism<TraversableT<U>, U>>} */(fb.map(b => next => impure(b, () =>
/** @type {Free<U>} */(next)
)))
))
}
}
run() {
return this.#next(this.#value).run()
}
}
/**
* @template T
* @param {T} x
* @returns {Pure<T>}
*/
const pure = x => new Pure(x)
/**
* @template T
* @param {T} x
* @param {(value: T) => Free<any>} f
* @returns {Impure<T>}
*/
const impure = (x, f) => new Impure(x, f)
/**
* @template T
* @param {T} x
* @returns Impure<T>
*/
export const liftF = x => impure(x, pure)
const TypeRef = () => { }
TypeRef.constructor.of = pure
export const Free = TypeRef