diff --git a/src/algebra/free.js b/src/algebra/free.js index faad1e4..ef01f5d 100644 --- a/src/algebra/free.js +++ b/src/algebra/free.js @@ -1,22 +1,23 @@ -import { Algebra, Foldable, Monad, Traversable } from './interfaces.js' -import { curry } from '../..//vendor/izuna/src/index.js' -import { Reducer } from './utilities.js' +import { Algebra, Monad } from './interfaces.js' -/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Foldable as FoldableT, Functor, Morphism, Traversable as TraversableT } from './types.js' */ +/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Functor, Morphism } from './types.js' */ -const kleisli = curry((f, g, x) => f(x).chain(g)) +const Interfaces = Algebra(Monad) /** * @template T - * @typedef {Pure | Impure} Free + * @typedef {Applicative & (Pure | Impure)} Free */ -/** @template T */ -export class Pure extends Algebra(Monad, Traversable) { +/** + * @template T + * @implements {Applicative} + */ +class Pure extends Interfaces { #value /** - * @param {T} value + * @param {T} value */ constructor(value) { super() @@ -24,7 +25,18 @@ export class Pure extends Algebra(Monad, Traversable) { } /** + * @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) @@ -34,165 +46,130 @@ export class Pure extends Algebra(Monad, Traversable) { * @type {Functor['map']} */ map(f) { - return pure(f(this.#value)) + return this.chain(x => pure(f(x))) } + /** * @template U * @type {Apply['ap']} - * @this {Pure>} - * @param {Pure} b - * @returns {Free} + * @param {Free>} b + * @returns {Free} */ ap(b) { - return /** @type {Free} */ (b.map(this.#value)) + return /** @type {Free} */ (b.chain(f => + /** @type {Chain} */(this.map(f)) + )) } - /** - * @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() { + interpret() { return this.#value } + + toString() { + return `Pure(${this.#value})` + } } /** - * @template T, [N=any] - * @implements Functor - * @implements Chain - * @implements TraversableT + * @template T + * @implements {Applicative} */ -export class Impure extends Algebra(Monad, Foldable, Traversable) { +class Impure extends Interfaces { #value #next /** - * @param {T} value - * @param {(value: T) => Free} f + * @param {T} value + * @param {(value: T) => Free} next */ - constructor(value, f) { + constructor(value, next) { super() this.#value = value - this.#next = f + this.#next = next + } + + /** + * @template T + * @param {T} value + */ + static of(value) { + return liftF(value) } /** - * @type {Chain['chain']} * @template U - * @param {(value: T) => Free} f + * @type {Chain['chain']} + * @param {Morphism>} f * @returns {Free} */ chain(f) { - return impure(this.#value, kleisli(this.#next, f)) + return /** @type {Free} */ (impure( + this.#value, + x => /** @type {Free} */(this.#next(x).chain(f)) + )) } /** - * @template {Functor} U + * @template U * @type {Functor['map']} - * @param {Morphism} f + * @param {Morphism} f * @returns {Free} */ map(f) { - return impure( + return /** @type {Free} */ (impure( this.#value, - y => /** @type {Free} */(f(y).map(f)) - ) + x => /** @type Free} */(this.#next(x).map(f)) + )) } /** * @template U * @type {Apply['ap']} - * @this {Free>} - * @param {Free} b + * @param {Free>} b * @returns {Free} */ ap(b) { - return /** @type {Free} */ (this.chain(f => - /** @type {Free} */(b.map(f)) + return /** @type {Free} */ (impure( + this.#value, + x => /** @type {Free} */(b.chain(f => + /** @type {Free} */(this.#next(x).map(f))) + ) )) } - /** - * @type {FoldableT['reduce']} - */ - reduce(f, acc) { - const Const = Reducer(f, acc) - // @ts-ignore - return this.traverse(Const, x => new Const(x)).value + interpret() { + return this.#next(this.#value).interpret() } - /** - * @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() + toString() { + return `Impure(${this.#value}, ${this.#next})` } } + /** * @template T - * @param {T} x - * @returns {Pure} + * @param {T} value */ -const pure = x => new Pure(x) +export const pure = value => new Pure(value) /** * @template T * @param {T} x * @param {(value: T) => Free} f - * @returns {Impure} */ -const impure = (x, f) => new Impure(x, f) +export const impure = (x, f) => new Impure(x, f) /** * @template T - * @param {T} x - * @returns Impure + * @param {T} value */ -export const liftF = x => impure(x, pure) +export const liftF = value => impure(value, pure) -const TypeRef = () => { } -TypeRef.constructor.of = pure - -export const Free = TypeRef +/** + * @template T + * @type {ApplicativeTypeRef>} + */ +export const Free = Pure diff --git a/src/algebra/list.js b/src/algebra/list.js index 7ea988b..b2dd033 100644 --- a/src/algebra/list.js +++ b/src/algebra/list.js @@ -1,74 +1,152 @@ import { Algebra, Comonad, Foldable, Monoid, Semigroup } from './interfaces.js' -/** @import { InferredMorphism, Morphism } from './types.js' */ +/** @import { Apply, Chain, Foldable as FoldableT, Functor, Morphism, Semigroup as SemigroupT } from './types.js' */ -class Empty extends Algebra(Semigroup, Foldable, Monoid, Comonad) { +const Interfaces = Algebra(Semigroup, Foldable, Monoid, Comonad) + +/** + * @template T + * @typedef {Element | Empty} List + */ + +/** @template T */ +class Empty extends Interfaces { constructor() { super() } /** - * @template T - * @this {Empty} - * @param {T} value - * @returns {List} + * @template U + * @type {Chain['chain']} + * @this {Empty} + * @param {Morphism>} _f + * @returns {List} */ - prepend(value) { - return new Element(value, () => this) - } - - head() { - return empty - } - - tail() { - return empty + chain(_f) { + return /** @type {List} */ (this) } /** - * @template T - * @param {List>} other - * @returns {List>} + * @template U + * @type {Functor['map']} + * @this {Empty} + * @param {Morphism} _f + * @returns {List} */ - concat(other) { - return other - } - - chain(f) { + map(_f) { return this } - map(f) { - return this + /** + * @template U + * @type {Apply['ap']} + * @this {Empty} + * @param {List>} _b + * @returns {List} + */ + ap(_b) { + return /** @type {List} */ (this) } + /** + * @type {SemigroupT['concat']} + */ + concat(b) { + return b + } + + /** + * @type {FoldableT['reduce']} + */ + reduce(_f, acc) { + return acc + } + + count() { + return 0 + } + + /** @returns {this is Empty} */ + isEmpty() { + return true + } } -/** - * @template T - */ -class Element extends Algebra(Semigroup, Foldable, Monoid, Comonad) { +/** @template T */ +class Element extends Interfaces { #head #tail + /** @type {List} */ + #cache /** * @param {T} head - * @param {() => List} tail + * @param {() => List} [tail] */ constructor(head, tail = List.empty) { super() this.#head = head this.#tail = tail + this.#cache = null + } + /** + * @template U + * @type {Chain['chain']} + * @this {Element} + * @param {Morphism>} f + * @returns {List} + */ + chain(f) { + return /** @type {List} */ (f(this.#head).concat( + /** @type {never} */(this.tail().chain(f)) + )) } /** - * @template T - * @this {Empty} - * @param {T} value - * @returns {List} + * @template U + * @type {Functor['map']} + * @param {Morphism} f + * @returns {List} */ - prepend(value) { - return new Element(value, () => this) + map(f) { + return new Element( + f(this.#head), + () => /** @type {List} */(this.tail().map(f)) + ) + } + + /** + * @template U + * @type {Apply['ap']} + * @this {Element} + * @param {List>} b + * @returns {List} + */ + ap(b) { + if (b.isEmpty()) { + return List.empty() + } + + const head = /** @type {List} */ (this.map(b.head())) + const rest = /** @type {List} */ (this.ap(b.tail())) + return /** @type {List} */ (head.concat(rest)) + } + + /** + * @type {SemigroupT['concat']} + */ + concat(b) { + return new Element( + this.#head, + () => /** @type {List} */(this.tail().concat(b)) + ) + } + + /** + * @type {FoldableT['reduce']} + */ + reduce(f, acc) { + return this.tail().reduce(f, f(acc, this.#head)) } head() { @@ -76,73 +154,34 @@ class Element extends Algebra(Semigroup, Foldable, Monoid, Comonad) { } tail() { - return this.#tail() - } - - /** - * @param {List} other - * @returns {List} - */ - concat(other) { - if (this.isEmpty()) { - return other - } else { - return new Element( - this.head(), - () => this.tail().concat(other) - ) - } - } - - /** - * @template U - * @param {Morphism>} f - * @returns {List} - */ - chain(f) { - return new Element( - f(this.head()), - this.tail().chain(f) - ) - } - - /** - * @template U - * @param {(acc: U, value: T) => U} f - * @param {U} init - * @returns {U} - */ - reduce(f, init) { - const acc = f(init, this._value) - return this.tail().reduce(f, acc) - } - - extract() { - return this.reduce(reduceArray, []) + return this.#cache || (this.#cache = this.#tail()) } count() { return this.tail().count() + 1 } - /** @returns {this is Empty} */ + /** @returns {this is Empty} */ isEmpty() { return false } + + toArray() { + return this.reduce( + reduceArray, + [] + ) + } } -/** - * @template T - * @type {Element | Empty} - */ -export class List { +class TypeRef { /** * @template T * @param {T} value * @returns {List} */ static of(value) { - return empty.prepend(value) + return new Element(value) } /** @@ -181,11 +220,25 @@ export class List { } static empty() { - return Empty + return empty } } +/** + * @template T + * @param {T[]} acc + * @param {T} x + * @returns {T[]} + */ +const reduceArray = (acc, x) => acc.concat(x) + +export const List = TypeRef + const empty = new Empty() +/** + * @template T + * @param {T} value + */ export const list = value => List.of(value) diff --git a/src/algebra/reader.js b/src/algebra/reader.js index 617584e..4e0c06b 100644 --- a/src/algebra/reader.js +++ b/src/algebra/reader.js @@ -1,7 +1,7 @@ import { id } from '../../vendor/izuna/src/index.js' import { Algebra, Monad } from './interfaces.js' -/** @import { Apply, Chain, Functor, InferredMorphism, Morphism } from './types.js' */ +/** @import { ApplicativeTypeRef, Apply, Chain, Functor, InferredMorphism, Morphism } from './types.js' */ /** * @template T @@ -21,6 +21,7 @@ export class Reader extends Algebra(Monad) { /** * @template T + * @type {ApplicativeTypeRef>['of']} * @param {T} a * @returns {Reader} */ @@ -34,13 +35,12 @@ export class Reader extends Algebra(Monad) { } /** - * @template U * @type {Functor['map']} - * @param {Morphism} f - * @returns {Reader} + * @param {InferredMorphism} f + * @returns {Reader} */ map(f) { - return new Reader(env => f(this.#run(env))) + return this.chain(value => Reader.of(f(value))) } /** @@ -49,19 +49,23 @@ export class Reader extends Algebra(Monad) { * @returns {Reader} */ chain(f) { - return new Reader(env => f(this.#run(env)).run(env)) + return new Reader(env => { + const result = this.#run(env) + const next = f(result) + return next.run(env) + }) } /** * @template U * @type {Apply['ap']} - * @param {Reader>} other + * @param {Reader>} b * @returns {Reader} */ - ap(other) { - return new Reader( - env => other.run(env)(this.run(env)) - ) + ap(b) { + return /** @type {Reader} */ (b.chain(f => + /** @type {Reader} */(this.map(f)) + )) } /** diff --git a/src/algebra/utilities.js b/src/algebra/utilities.js index 88cdb12..6472900 100644 --- a/src/algebra/utilities.js +++ b/src/algebra/utilities.js @@ -4,23 +4,14 @@ * @param {U} acc */ export const Reducer = (f, acc) => { - /** @template A */ - return class Const { - /** @param {A} value */ - constructor(value) { - this.value = value - } - - static of() { return new Const(acc) } - - map() { return this } - - /** - * @this {Const} - * @param {Const} b - */ - ap(b) { - return new Const(f(b.value, this.value)) - } + function Const(value) { + this.value = value } + + Const.of = function() { return new Const(acc) } + Const.prototype.map = function() { return this } + Const.prototype.ap = function(b) { + return new Const(f(b.value, this.value)) + } + return Const }