types & free reduce

This commit is contained in:
Rowan 2025-04-11 04:03:09 -05:00
parent 853601f2c5
commit cf3fe66f73
3 changed files with 74 additions and 20 deletions

View file

@ -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 { 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' */ /** @import { InferredMorphism } from './types.js' */
@ -12,7 +12,7 @@ const kleisli = curry((f, g, x) => f(x).chain(g))
* @template T * @template T
* @type {Pure<T> | Impure<T>} Free * @type {Pure<T> | Impure<T>} Free
*/ */
export class Free extends Algebra(Monad) { export class Free {
/** /**
* @template T * @template T
* @param {T} value * @param {T} value
@ -23,7 +23,7 @@ export class Free extends Algebra(Monad) {
} }
/** @template T */ /** @template T */
export class Pure extends Free { export class Pure extends Algebra(Monad, Traversable) {
#value #value
/** /**
@ -48,13 +48,29 @@ export class Pure extends Free {
return pure(f(this.#value)) return pure(f(this.#value))
} }
/**
* @template U
* @this {Pure<Morphism<U, T>>}
* @param {Free<T>} b
*/
ap(b) {
return b.map(this.#value)
}
/**
* @type {FoldableT<T>['reduce']}
*/
reduce(f, acc) {
return f(acc, this.#value)
}
/** /**
* @template U * @template U
* @template {Applicative<U>} M * @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} TypeRef * @template {ApplicativeTypeRef<U, M>} TypeRef
* @param {TypeRef} _A * @param {TypeRef} _A
* @param {*} f * @param {(value: T) => M} f
* @returns {M} * @returns {Applicative<Free<U>>}
*/ */
traverse(_A, f) { traverse(_A, f) {
return f(this.#value).map(pure) 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<T>
* @implements Chain<T>
* @implements TraversableT<T>
*/
export class Impure extends Algebra(Monad, Foldable, Traversable) {
#value #value
#next #next
/** /**
* @param {T} value * @param {T} value
* @param {(value: T) => Free<any>} f * @param {(value: T) => Free<N>} f
*/ */
constructor(value, f) { constructor(value, f) {
super() super()
@ -81,26 +102,56 @@ export class Impure extends Free {
} }
/** /**
* @param {<U>(value: T) => Free<U>} f * @type {Chain<T>['chain']}
* @template U
* @param {(value: T) => Free<U>} f
* @returns {Chain<T>}
*/ */
chain(f) { chain(f) {
return impure(this.#value, kleisli(this.#next, f)) return impure(this.#value, kleisli(this.#next, f))
} }
/**
* @template {Functor<T>} U
* @type {Functor<T>['map']}
* @param {(value: T) => U} f
* @returns {Functor<T>}
*/
map(f) { map(f) {
return impure( return impure(
this.#value, this.#value,
y => f(y).chain(f) y => f(y).map(f)
) )
} }
/** /**
* @type {Apply<T>['ap']}
*/
ap(b) {
return this.chain(f => b.map(f))
}
/**
* @type {FoldableT<T>['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<T>['traverse']}
* @template U * @template U
* @template {Applicative<U>} M * @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} TypeRef * @template {ApplicativeTypeRef<U, M>} TypeRef
* @this {Impure<T, N>}
* @param {TypeRef} A * @param {TypeRef} A
* @param {(value: T) => M} f * @param {(value: T | N) => M} f
* @returns {M} * @returns {Applicative<Free<U>>}
*/ */
traverse(A, f) { traverse(A, f) {
const fb = f(this.#value) const fb = f(this.#value)
@ -131,6 +182,7 @@ const pure = x => new Pure(x)
*/ */
const impure = (x, f) => new Impure(x, f) const impure = (x, f) => new Impure(x, f)
/** /**
* @template T * @template T
* @param {T} x * @param {T} x

View file

@ -100,9 +100,10 @@ export default {}
/** /**
* @template T * @template T
* @typedef {{ * @typedef {
ap: <U>(f: Apply<Morphism<T, U>>) => Apply<U> Functor<T> &
* }} Apply { ap: <U>(f: Apply<Morphism<T, U>>) => Apply<U> }
* } Apply
*/ */
/** /**
@ -157,7 +158,7 @@ export default {}
/** /**
* @template T * @template T
* @typedef {{ * @typedef {{
filter: <U>(f: (acc: U, val: T) => U, init: U) => U reduce: <U>(f: (acc: U, val: T) => U, init: U) => U
* }} Foldable * }} Foldable
*/ */

View file

@ -77,15 +77,16 @@ function is(other) {
} }
/** /**
* @template {PropertyKey} const T * @template {string} const T
* @template {Array<PropertyKey>} const U * @template {Array<PropertyKey>} const U
* @param {T} typeName * @param {T} typeName
* @param {...U} variantNames * @param {...U} variantNames
* @returns {Union<T, U>} * @returns {Union<T, U>}
*/ */
export const Union = (typeName, variantNames) => { export const Union = (typeName, variantNames) => {
const tag = { [Tag]: typeName, is } const typeTag = Symbol(typeName)
const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeName, v)])) const tag = { [Tag]: typeTag, is }
const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeTag, v)]))
const result = Object.assign(tag, variants) const result = Object.assign(tag, variants)