free refactor

This commit is contained in:
Rowan 2025-04-07 23:05:39 -05:00
parent 55be1efee1
commit f79a2ca462
4 changed files with 256 additions and 230 deletions

View file

@ -1,5 +1,6 @@
import { curry } from '../curry.js'
import { Suspend, suspend, pure } from './free.js'
import { concat } from '../iter.js'
import { Impure, pure, impure } from './free.js'
/** @import {InferredMorphism, Morphism, ChainConstructor} from './types.js' */
@ -32,12 +33,14 @@ export const kleisliCompose = curry(
(f, g, x) => f(x).chain(g)
)
export const lift = (...args) => args
/**
* @template T
* @param {T} x
* @returns {Suspend<T>}
* @returns {Impure<T>}
*/
export const liftF = x => suspend(
export const liftF = x => impure(
x,
/** @type {InferredMorphism<T>} */(pure)
)
@ -47,5 +50,18 @@ export const liftF = x => suspend(
* @param {number} b
* @returns {number}
*/
export const sum = (a, b) => a + b
export const add = (a, b) => a + b
/**
* @template T
* @param {T} x
* @returns {() => T}
*/
export const thunk = x => () => x
export const prepend = (x, xs) => concat([x], xs)
export const type = x => typeof x
export const is = (ctr, x) => x.constructor === ctr

View file

@ -1,164 +1,163 @@
import { liftF } from './fn.js'
import { Algebra, BaseSet, Monad, Semigroupoid } from './index.js'
import { Option } from './option.js'
import { thunk } from './fn.js'
/** @import { Morphism } from './types.js' */
/** @import { InferredMorphism, Morphism } from './types.js' */
/**
* @template T
* @extends BaseSet
* @mixes Monad
* @template A
* @typedef {Pure<A> | Impure<A>} Free
*/
export class Suspend extends Algebra(Semigroupoid, Monad) {
_value
#fn
/** @template A */
export class Pure {
#value
/**
* @param {T} value
* @param {InferredMorphism<T>} fn
* @param {A} value
*/
constructor(value, fn) {
super()
this._value = value
this.#fn = fn
}
/** @returns {this is Pure<T>} */
isPure() { return false }
/** @returns {this is Suspend<T>} */
isSuspend() { return true }
/**
* @template U
* @param {Morphism<T, Free<U>>} f
* @returns {Suspend<T>}
*/
chain(f) {
return new Suspend(this._value, x => this.#fn(x).chain(f))
}
/**
* @template U
* @param {Morphism<T, U>} g
* @returns {Suspend<T>}
*/
map(g) {
return new Suspend(this._value, x => this.#fn(x).map(g))
}
/**
* @template U
* @param {Morphism<T, U>} g
* @returns {Suspend<T>}
*/
then(g) {
return this.map(g)
}
/**
* @template T
* @param {InferredMorphism<T>} other
*/
compose(other) {
return this.chain(() => other)
}
step() {
return this.#fn(this._value)
}
run() {
return this.step().run()
}
}
/** @template T */
export class Pure extends Algebra(Monad) {
_value
/** @param {T} value */
constructor(value) {
super()
this._value = value
this.#value = value
}
/** @returns {this is Pure<T>} */
isPure() { return true }
/** @returns {this is Suspend<T>} */
isSuspend() { return false }
/**
* @template U
* @param {Morphism<T, Pure<U>>} f
* @returns {Pure<U>}
* @template {Free<B>} B
* @param {Morphism<A, B>} f
* @returns {B}
*/
chain(f) {
return f(this._value)
console.log('Pure.chain', this.#value)
return f(this.#value)
}
/**
* @template U
* @param {Morphism<T, U>} f
* @returns {Pure<U>}
* @template B
* @param {Morphism<A, B>} f
* @returns {Free<B>}
*/
map(f) {
return this.chain(x => pure(f(x)))
console.log(`Pure.map ${f} ${this.#value}`)
// @ts-ignore
return pure(f(this.#value))
}
/**
* @template R
* @param {Morphism<T, R>} f
* @returns {Pure<R>}
* @template B, C
* @param {Free<B>} b
* @returns {Free<C>}
*/
then(f) {
return this.map(f)
ap(b) {
console.log('Pure.ap', b, this.#value)
return b.map(this.#value)
}
traverse(f, A) {
return A.of(this.map(f))
}
/**
* @template T
* @param {InferredMorphism<T>} other
* @template B
* @param {(acc: B, value: A) => B} f
* @param {B} init
* @returns {B}
*/
compose(other) {
return this.chain(() => pure(other))
reduce(f, init) {
console.log('Pure.reduce', init, this.#value)
return f(init, this.#value)
}
step() {
return this._value
}
run() {
return this._value
/**
* @template A
* @typedef {<B>(value: A) => Free<B>} Computation
*/
/** @template A */
export class Impure {
#next
/**
* @param {() => Free<any>} next
*/
constructor(next) {
this.#next = next
}
/**
* @template {Free<B>} B
* @param {Morphism<A, B>} f
* @returns {B}
*/
chain(f) {
console.log('Impure.chain')
// @ts-ignore
return new Impure(
// @ts-ignore
() => {
console.log(`Impure.chain<${f}>`, this.#next())
this.#next().chain(f)
}
//() => this.#next().chain(f)
)
}
/**
* @template B
* @param {Morphism<A, B>} f
* @returns {Free<B>}
*/
map(f) {
console.log(`Impure.map ${f}`)
return new Impure(
// @ts-ignore
() => {
console.log(`Impure.map<${f}>`, this.#next())
return this.#next().map(f)
}
//() => this.#next().map(f)
)
}
/**
* @template B
* @param {Free<any>} b
* @returns {Free<B>}
*/
ap(b) {
console.log('Impure.ap', b)
return new Impure(
// @ts-ignore
() => b.map(this.#next())
)
}
traverse(f, A) {
return A.of(liftF(
this.#next().traverse(f, A)
))
}
/**
* @template B
* @param {(acc: B, value: A) => B} f
* @param {B} acc
* @returns {B}
*/
reduce(f, acc) {
}
}
/**
* @template T
* @type {Pure<T> | Suspend<T>} Free
* @template A
* @param {A} x
* @returns {Free<A, A>}
*/
export class Free {
/**
* @template T
* @param {T} value
* @returns {Free<T>}
*/
static of(value) {
return liftF(value)
}
}
export const pure = x => new Pure(x)
/**
* @template T
* @param {T} value
* @returns {Pure<T>}
* @template A, B
* @param {Computation<A, B>} f
*/
export const pure = value => new Pure(value)
/**
* @template T
* @param {T} value
* @param {InferredMorphism<T>} f
* @returns {Suspend<T>}
*/
export const suspend = (value, f) => new Suspend(value, f)
export const impure = f => new Impure(f)
export const liftF = effect => impure(thunk(effect))

View file

@ -7,7 +7,7 @@ export class IO extends Algebra(Monad) {
_effect
/**
* @type {T} effect
* @param {T} effect
*/
constructor(effect) {
super()
@ -38,12 +38,11 @@ export class IO extends Algebra(Monad) {
/**
* @template {Fn} U
* @this {IO<T>}
* @param {IO<Morphism<T, U>>} other
* @returns {IO<U>}
* @returns {IO<T>}
*/
ap(other) {
return IO.of(() => other.run()(this.run()))
return /** @type {IO<T>} */ (IO.of((() => other.run()(this.run()))))
}
run() {

View file

@ -1,112 +1,110 @@
import { Pure, Suspend } from './free.js'
import { AlgebraWithBase, Category, Comonad, Foldable, Monoid } from './index.js'
import { Algebra, AlgebraWithBase, Comonad, Foldable, Monoid, Semigroup } from './index.js'
/** @import { InferredMorphism, Morphism } from './types.js' */
const Nil = Symbol('Nil')
class Empty extends Algebra(Semigroup, Foldable, Monoid, Comonad) {
constructor() {
super()
}
/**
* @template T
* @extends {Pure<T>}
* @this {Empty}
* @param {T} value
* @returns {List<T>}
*/
class ListPure extends AlgebraWithBase(Pure)(Category, Foldable, Monoid) {
static empty() {
return List.empty()
}
static id() {
return List.empty()
prepend(value) {
return new Element(value, () => this)
}
head() {
return this._value
return empty
}
tail() {
return Empty
}
/**
* @template U
* @this {ListPure<T>}
* @param {Morphism<T, ListPure<U>>} f
* @returns {ListPure<U>}
*/
chain(f) {
return f(this.head())
}
/**
* @template U
* @this {ListPure<T>}
* @param {Morphism<T, U>} f
* @returns {ListPure<U>}
*/
map(f) {
return new ListPure(f(this._value))
}
/**
* @template U
* @param {(acc: U, value: T) => U} f
* @param {U} init
* @returns {U}
*/
reduce(f, init) {
return f(init, this._value)
}
count() {
return this.isEmpty() ? 0 : 1
}
/**
* @this {List<any>}
* @returns {this is Empty}
*/
isEmpty() {
return this === Empty
}
return empty
}
/**
* @template T
* @extends {Suspend<T>}
* @param {List<Element<T>>} other
* @returns {List<Element<T>>}
*/
class ListSuspend extends AlgebraWithBase(Suspend)(Foldable, Monoid, Comonad) {
concat(other) {
return other
}
chain(f) {
return this
}
map(f) {
return this
}
}
/**
* @template T
*/
class Element extends Algebra(Semigroup, Foldable, Monoid, Comonad) {
#head
#tail
/**
* @param {T} head
* @param {() => List<T>} tail
*/
constructor(head, tail) {
super(
head,
/** @type {InferredMorphism<T>} */(tail)
)
}
static empty() {
return List.empty()
constructor(head, tail = List.empty) {
super()
this.#head = head
this.#tail = tail
}
/**
* @template T
* @this {ListSuspend<T>}
* @this {Empty}
* @param {T} value
* @returns {ListSuspend<T>}
* @returns {List<T>}
*/
cons(value) {
return new ListSuspend(value, () => this)
prepend(value) {
return new Element(value, () => this)
}
head() {
return this._value
return this.#head
}
tail() {
return this.step()
return this.#tail()
}
/**
* @param {List<T>} other
* @returns {List<T>}
*/
concat(other) {
if (this.isEmpty()) {
return other
} else {
return new Element(
this.head(),
() => this.tail().concat(other)
)
}
}
/**
* @template U
* @param {Morphism<T, List<U>>} f
* @returns {List<U>}
*/
chain(f) {
return new Element(
f(this.head()),
this.tail().chain(f)
)
}
/**
@ -136,7 +134,7 @@ class ListSuspend extends AlgebraWithBase(Suspend)(Foldable, Monoid, Comonad) {
/**
* @template T
* @type {ListPure<T> | ListSuspend<T>} List
* @type {Element<T> | Empty}
*/
export class List {
/**
@ -145,7 +143,7 @@ export class List {
* @returns {List<T>}
*/
static of(value) {
return new ListSuspend(value, List.empty)
return empty.prepend(value)
}
/**
@ -169,16 +167,26 @@ export class List {
if (next.done) {
return List.empty()
} else {
return new ListSuspend(next.value, () => List.fromIterator(iterator))
return new Element(next.value, () => List.fromIterator(iterator))
}
}
/**
* @template T
* @param {T} head
* @param {List<T>} tail
* @returns {List<T>}
*/
static cons(head, tail) {
return new Element(head, () => tail)
}
static empty() {
return Empty
}
}
const Empty = new ListPure(Nil)
const empty = new Empty()
/**
* @template T
@ -188,3 +196,7 @@ const Empty = new ListPure(Nil)
*/
const reduceArray = (acc, value) => acc.concat(value)
const arr = Array.from({ length: 10 })
const list = List.from(arr)
list.map(x => x * 2)