From cf3fe66f73623e78ff7757912c172cf494610562 Mon Sep 17 00:00:00 2001 From: rowan Date: Fri, 11 Apr 2025 04:03:09 -0500 Subject: [PATCH] types & free reduce --- src/algebra/free.js | 78 ++++++++++++++++++++++++++++++++++++-------- src/algebra/types.js | 9 ++--- src/union.js | 7 ++-- 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/algebra/free.js b/src/algebra/free.js index 536ed01..dd5d584 100644 --- a/src/algebra/free.js +++ b/src/algebra/free.js @@ -1,7 +1,7 @@ -import { Algebra, Monad } from './interfaces.js' +import { Algebra, Foldable, Monad, Traversable } from './interfaces.js' import { curry } from '../..//vendor/izuna/src/index.js' -/** @import { Applicative, ApplicativeTypeRef, Morphism } from './types.js' */ +/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Foldable as FoldableT, Functor, Monad as MonadT, Morphism, Traversable as TraversableT } from './types.js' */ /** @import { InferredMorphism } from './types.js' */ @@ -12,7 +12,7 @@ const kleisli = curry((f, g, x) => f(x).chain(g)) * @template T * @type {Pure | Impure} Free */ -export class Free extends Algebra(Monad) { +export class Free { /** * @template T * @param {T} value @@ -23,7 +23,7 @@ export class Free extends Algebra(Monad) { } /** @template T */ -export class Pure extends Free { +export class Pure extends Algebra(Monad, Traversable) { #value /** @@ -48,13 +48,29 @@ export class Pure extends Free { return pure(f(this.#value)) } + /** + * @template U + * @this {Pure>} + * @param {Free} b + */ + ap(b) { + return b.map(this.#value) + } + + /** + * @type {FoldableT['reduce']} + */ + reduce(f, acc) { + return f(acc, this.#value) + } + /** * @template U * @template {Applicative} M * @template {ApplicativeTypeRef} TypeRef * @param {TypeRef} _A - * @param {*} f - * @returns {M} + * @param {(value: T) => M} f + * @returns {Applicative>} */ traverse(_A, f) { return f(this.#value).map(pure) @@ -65,14 +81,19 @@ export class Pure extends Free { } } -/** @template T */ -export class Impure extends Free { +/** + * @template T, [N=any] + * @implements Functor + * @implements Chain + * @implements TraversableT + */ +export class Impure extends Algebra(Monad, Foldable, Traversable) { #value #next /** * @param {T} value - * @param {(value: T) => Free} f + * @param {(value: T) => Free} f */ constructor(value, f) { super() @@ -81,26 +102,56 @@ export class Impure extends Free { } /** - * @param {(value: T) => Free} f + * @type {Chain['chain']} + * @template U + * @param {(value: T) => Free} f + * @returns {Chain} */ chain(f) { return impure(this.#value, kleisli(this.#next, f)) } + /** + * @template {Functor} U + * @type {Functor['map']} + * @param {(value: T) => U} f + * @returns {Functor} + */ map(f) { return impure( this.#value, - y => f(y).chain(f) + y => f(y).map(f) ) } /** + * @type {Apply['ap']} + */ + ap(b) { + return this.chain(f => 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)) + ) + } + + /** + * @type {TraversableT['traverse']} * @template U * @template {Applicative} M * @template {ApplicativeTypeRef} TypeRef + * @this {Impure} * @param {TypeRef} A - * @param {(value: T) => M} f - * @returns {M} + * @param {(value: T | N) => M} f + * @returns {Applicative>} */ traverse(A, f) { const fb = f(this.#value) @@ -131,6 +182,7 @@ const pure = x => new Pure(x) */ const impure = (x, f) => new Impure(x, f) + /** * @template T * @param {T} x diff --git a/src/algebra/types.js b/src/algebra/types.js index d8b95d6..9378ddf 100644 --- a/src/algebra/types.js +++ b/src/algebra/types.js @@ -100,9 +100,10 @@ export default {} /** * @template T - * @typedef {{ - ap: (f: Apply>) => Apply - * }} Apply + * @typedef { + Functor & + { ap: (f: Apply>) => Apply } + * } Apply */ /** @@ -157,7 +158,7 @@ export default {} /** * @template T * @typedef {{ - filter: (f: (acc: U, val: T) => U, init: U) => U + reduce: (f: (acc: U, val: T) => U, init: U) => U * }} Foldable */ diff --git a/src/union.js b/src/union.js index b5e6a39..4c95d0b 100644 --- a/src/union.js +++ b/src/union.js @@ -77,15 +77,16 @@ function is(other) { } /** - * @template {PropertyKey} const T + * @template {string} const T * @template {Array} const U * @param {T} typeName * @param {...U} variantNames * @returns {Union} */ export const Union = (typeName, variantNames) => { - const tag = { [Tag]: typeName, is } - const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeName, v)])) + const typeTag = Symbol(typeName) + const tag = { [Tag]: typeTag, is } + const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeTag, v)])) const result = Object.assign(tag, variants)