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 { 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' */ /** @import {InferredMorphism, Morphism, ChainConstructor} from './types.js' */
@ -32,12 +33,14 @@ export const kleisliCompose = curry(
(f, g, x) => f(x).chain(g) (f, g, x) => f(x).chain(g)
) )
export const lift = (...args) => args
/** /**
* @template T * @template T
* @param {T} x * @param {T} x
* @returns {Suspend<T>} * @returns {Impure<T>}
*/ */
export const liftF = x => suspend( export const liftF = x => impure(
x, x,
/** @type {InferredMorphism<T>} */(pure) /** @type {InferredMorphism<T>} */(pure)
) )
@ -47,5 +50,18 @@ export const liftF = x => suspend(
* @param {number} b * @param {number} b
* @returns {number} * @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 { Option } from './option.js'
import { Algebra, BaseSet, Monad, Semigroupoid } from './index.js' import { thunk } from './fn.js'
/** @import { Morphism } from './types.js' */
/** @import { InferredMorphism, Morphism } from './types.js' */
/** /**
* @template T * @template A
* @extends BaseSet * @typedef {Pure<A> | Impure<A>} Free
* @mixes Monad
*/ */
export class Suspend extends Algebra(Semigroupoid, Monad) {
_value /** @template A */
#fn export class Pure {
#value
/** /**
* @param {T} value * @param {A} value
* @param {InferredMorphism<T>} fn
*/ */
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) { 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 * @template {Free<B>} B
* @param {Morphism<T, Pure<U>>} f * @param {Morphism<A, B>} f
* @returns {Pure<U>} * @returns {B}
*/ */
chain(f) { chain(f) {
return f(this._value) console.log('Pure.chain', this.#value)
return f(this.#value)
} }
/** /**
* @template U * @template B
* @param {Morphism<T, U>} f * @param {Morphism<A, B>} f
* @returns {Pure<U>} * @returns {Free<B>}
*/ */
map(f) { 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 * @template B, C
* @param {Morphism<T, R>} f * @param {Free<B>} b
* @returns {Pure<R>} * @returns {Free<C>}
*/ */
then(f) { ap(b) {
return this.map(f) console.log('Pure.ap', b, this.#value)
return b.map(this.#value)
}
traverse(f, A) {
return A.of(this.map(f))
} }
/** /**
* @template T * @template B
* @param {InferredMorphism<T>} other * @param {(acc: B, value: A) => B} f
* @param {B} init
* @returns {B}
*/ */
compose(other) { reduce(f, init) {
return this.chain(() => pure(other)) 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 * @template A
* @type {Pure<T> | Suspend<T>} Free * @param {A} x
* @returns {Free<A, A>}
*/ */
export class Free { export const pure = x => new Pure(x)
/**
* @template T
* @param {T} value
* @returns {Free<T>}
*/
static of(value) {
return liftF(value)
}
}
/** /**
* @template T * @template A, B
* @param {T} value * @param {Computation<A, B>} f
* @returns {Pure<T>}
*/ */
export const pure = value => new Pure(value) export const impure = f => new Impure(f)
export const liftF = effect => impure(thunk(effect))
/**
* @template T
* @param {T} value
* @param {InferredMorphism<T>} f
* @returns {Suspend<T>}
*/
export const suspend = (value, f) => new Suspend(value, f)

View file

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

View file

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