diff --git a/src/algebra/free.js b/src/algebra/free.js index dd5d584..ca81457 100644 --- a/src/algebra/free.js +++ b/src/algebra/free.js @@ -1,7 +1,8 @@ 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, Monad as MonadT, Morphism, Traversable as TraversableT } from './types.js' */ +/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Comonad, Foldable as FoldableT, Functor, Monad as MonadT, Morphism, Traversable as TraversableT } from './types.js' */ /** @import { InferredMorphism } from './types.js' */ @@ -10,17 +11,9 @@ const kleisli = curry((f, g, x) => f(x).chain(g)) /** * @template T - * @type {Pure | Impure} Free + * @typedef {Pure | Impure} Free */ -export class Free { - /** - * @template T - * @param {T} value - */ - static of(value) { - return new Pure(value) - } -} +const Free = {} /** @template T */ export class Pure extends Algebra(Monad, Traversable) { @@ -35,14 +28,14 @@ export class Pure extends Algebra(Monad, Traversable) { } /** - * @param {InferredMorphism} f + * @type {Chain['chain']} */ chain(f) { return f(this.#value) } /** - * @param {InferredMorphism} f + * @type {Functor['map']} */ map(f) { return pure(f(this.#value)) @@ -50,11 +43,13 @@ export class Pure extends Algebra(Monad, Traversable) { /** * @template U + * @type {Apply['ap']} * @this {Pure>} - * @param {Free} b + * @param {Pure} b + * @returns {Free} */ ap(b) { - return b.map(this.#value) + return /** @type {Free} */ (b.map(this.#value)) } /** @@ -105,7 +100,7 @@ export class Impure extends Algebra(Monad, Foldable, Traversable) { * @type {Chain['chain']} * @template U * @param {(value: T) => Free} f - * @returns {Chain} + * @returns {Free} */ chain(f) { return impure(this.#value, kleisli(this.#next, f)) @@ -114,33 +109,36 @@ export class Impure extends Algebra(Monad, Foldable, Traversable) { /** * @template {Functor} U * @type {Functor['map']} - * @param {(value: T) => U} f - * @returns {Functor} + * @param {Morphism} f + * @returns {Free} */ map(f) { return impure( this.#value, - y => f(y).map(f) + y => /** @type {Free} */(f(y).map(f)) ) } /** + * @template U * @type {Apply['ap']} + * @this {Free>} + * @param {Free} b + * @returns {Free} */ ap(b) { - return this.chain(f => b.map(f)) + return /** @type {Free} */ (this.chain(f => + /** @type {Free} */(b.map(f)) + )) } /** * @type {FoldableT['reduce']} */ reduce(f, acc) { - const fb = f(acc, this.#value) - const rest = this.#next(this.#value).reduce(f, acc) - - return rest.ap( - fb.map(b => next => impure(b, () => next)) - ) + const Const = Reducer(f, acc) + // @ts-ignore + return this.traverse(Const, x => new Const(x)).value } /** @@ -154,12 +152,20 @@ export class Impure extends Algebra(Monad, Foldable, Traversable) { * @returns {Applicative>} */ traverse(A, f) { - const fb = f(this.#value) - const rest = this.#next(this.#value).traverse(A, f) + const next = this.#next(this.#value) - return rest.ap( - fb.map(b => next => impure(b, () => next)) - ) + 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( + fb.map(b => next => impure(b, () => + /** @type {Free} */(next) + )) + )) + } } run() { @@ -192,6 +198,6 @@ export const liftF = x => impure(x, pure) import { id, Identity } from './identity.js' import { Option, some } from './option.js' -const a = liftF(1).chain(() => liftF(2)).traverse(Option, x => some(1)) -console.log(a.chain(x => x).run()) +const a = liftF(1).chain(() => liftF(2)).reduce((acc, x) => acc + x, 0) +console.log(a) diff --git a/src/algebra/identity.js b/src/algebra/identity.js index d892b41..efba1b3 100644 --- a/src/algebra/identity.js +++ b/src/algebra/identity.js @@ -1,6 +1,6 @@ import { Algebra, Comonad, Monad } from './interfaces.js' -/** @import { Morphism } from './types.js' */ +/** @import { Apply, Morphism } from './types.js' */ /** @template T */ export class Identity extends Algebra(Monad, Comonad) { @@ -28,17 +28,18 @@ export class Identity extends Algebra(Monad, Comonad) { * @param {Morphism} f */ map(f) { + console.log(this.toString(), f.toString()) return id(f(this.#value)) } /** * @template U - * @this {Identity>} - * @param {Identity} b - * @returns {Identity} + * @type {Apply['ap']} + * @param {Apply>} b + * @returns {Apply} */ ap(b) { - return b.map(this.#value) + return /** @type {Apply} */ (b.map(f => f(this.#value))) } /** @@ -59,6 +60,10 @@ export class Identity extends Algebra(Monad, Comonad) { extract() { return this.#value } + + toString() { + return `Identity(${this.#value})` + } } export const id = value => new Identity(value) diff --git a/src/algebra/utilities.js b/src/algebra/utilities.js new file mode 100644 index 0000000..88cdb12 --- /dev/null +++ b/src/algebra/utilities.js @@ -0,0 +1,26 @@ +/** + * @template T, U + * @param {(acc: U, value: T) => U} f + * @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)) + } + } +}