add reduce derivation; add reduce to Free; fix traverse

This commit is contained in:
Rowan 2025-04-11 16:37:04 -05:00
parent cf3fe66f73
commit 619f24c7dc
3 changed files with 75 additions and 38 deletions

View file

@ -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<T> | Impure<T>} Free
* @typedef {Pure<T> | Impure<T>} 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<T>} f
* @type {Chain<T>['chain']}
*/
chain(f) {
return f(this.#value)
}
/**
* @param {InferredMorphism<T>} f
* @type {Functor<T>['map']}
*/
map(f) {
return pure(f(this.#value))
@ -50,11 +43,13 @@ export class Pure extends Algebra(Monad, Traversable) {
/**
* @template U
* @type {Apply<T>['ap']}
* @this {Pure<Morphism<U, T>>}
* @param {Free<T>} b
* @param {Pure<U>} b
* @returns {Free<T>}
*/
ap(b) {
return b.map(this.#value)
return /** @type {Free<T>} */ (b.map(this.#value))
}
/**
@ -105,7 +100,7 @@ export class Impure extends Algebra(Monad, Foldable, Traversable) {
* @type {Chain<T>['chain']}
* @template U
* @param {(value: T) => Free<U>} f
* @returns {Chain<T>}
* @returns {Free<T>}
*/
chain(f) {
return impure(this.#value, kleisli(this.#next, f))
@ -114,33 +109,36 @@ export class Impure extends Algebra(Monad, Foldable, Traversable) {
/**
* @template {Functor<T>} U
* @type {Functor<T>['map']}
* @param {(value: T) => U} f
* @returns {Functor<T>}
* @param {Morphism<T, U>} f
* @returns {Free<T>}
*/
map(f) {
return impure(
this.#value,
y => f(y).map(f)
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 this.chain(f => b.map(f))
return /** @type {Free<T>} */ (this.chain(f =>
/** @type {Free<T>} */(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))
)
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<Free<U>>}
*/
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<Free<U>>} */ (rest.ap(
fb.map(b => next => impure(b, () =>
/** @type {Free<U>} */(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)

View file

@ -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<T, U>} f
*/
map(f) {
console.log(this.toString(), f.toString())
return id(f(this.#value))
}
/**
* @template U
* @this {Identity<Morphism<U, T>>}
* @param {Identity<U>} b
* @returns {Identity<T>}
* @type {Apply<T>['ap']}
* @param {Apply<Morphism<T, U>>} b
* @returns {Apply<U>}
*/
ap(b) {
return b.map(this.#value)
return /** @type {Apply<U>} */ (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)

26
src/algebra/utilities.js Normal file
View file

@ -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<T>}
* @param {Const<U>} b
*/
ap(b) {
return new Const(f(b.value, this.value))
}
}
}