diff --git a/src/algebra/fn.js b/src/algebra/fn.js index 3a877d0..75e3f0d 100644 --- a/src/algebra/fn.js +++ b/src/algebra/fn.js @@ -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} + * @returns {Impure} */ -export const liftF = x => suspend( +export const liftF = x => impure( x, /** @type {InferredMorphism} */(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 + diff --git a/src/algebra/free.js b/src/algebra/free.js index be54b1e..002ab0b 100644 --- a/src/algebra/free.js +++ b/src/algebra/free.js @@ -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 | Impure} Free */ -export class Suspend extends Algebra(Semigroupoid, Monad) { - _value - #fn + +/** @template A */ +export class Pure { + #value /** - * @param {T} value - * @param {InferredMorphism} fn + * @param {A} value */ - constructor(value, fn) { - super() - this._value = value - this.#fn = fn - } - - /** @returns {this is Pure} */ - isPure() { return false } - - /** @returns {this is Suspend} */ - isSuspend() { return true } - - /** - * @template U - * @param {Morphism>} f - * @returns {Suspend} - */ - chain(f) { - return new Suspend(this._value, x => this.#fn(x).chain(f)) - } - - /** - * @template U - * @param {Morphism} g - * @returns {Suspend} - */ - map(g) { - return new Suspend(this._value, x => this.#fn(x).map(g)) - } - - /** - * @template U - * @param {Morphism} g - * @returns {Suspend} - */ - then(g) { - return this.map(g) - } - - /** - * @template T - * @param {InferredMorphism} 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} */ - isPure() { return true } - - /** @returns {this is Suspend} */ - isSuspend() { return false } - /** - * @template U - * @param {Morphism>} f - * @returns {Pure} + * @template {Free} B + * @param {Morphism} f + * @returns {B} */ chain(f) { - return f(this._value) + console.log('Pure.chain', this.#value) + return f(this.#value) } /** - * @template U - * @param {Morphism} f - * @returns {Pure} + * @template B + * @param {Morphism} f + * @returns {Free} */ 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} f - * @returns {Pure} + * @template B, C + * @param {Free} b + * @returns {Free} */ - 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} 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 +} + +/** + * @template A + * @typedef {(value: A) => Free} Computation + */ + +/** @template A */ +export class Impure { + #next + + /** + * @param {() => Free} next + */ + constructor(next) { + this.#next = next } - run() { - return this._value + /** + * @template {Free} B + * @param {Morphism} 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} f + * @returns {Free} + */ + 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} b + * @returns {Free} + */ + 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 | Suspend} Free + * @template A + * @param {A} x + * @returns {Free} */ -export class Free { - /** - * @template T - * @param {T} value - * @returns {Free} - */ - static of(value) { - return liftF(value) - } -} +export const pure = x => new Pure(x) /** - * @template T - * @param {T} value - * @returns {Pure} + * @template A, B + * @param {Computation} f */ -export const pure = value => new Pure(value) - -/** - * @template T - * @param {T} value - * @param {InferredMorphism} f - * @returns {Suspend} - */ -export const suspend = (value, f) => new Suspend(value, f) +export const impure = f => new Impure(f) +export const liftF = effect => impure(thunk(effect)) diff --git a/src/algebra/io.js b/src/algebra/io.js index a5e0782..3fa4971 100644 --- a/src/algebra/io.js +++ b/src/algebra/io.js @@ -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} * @param {IO>} other - * @returns {IO} + * @returns {IO} */ ap(other) { - return IO.of(() => other.run()(this.run())) + return /** @type {IO} */ (IO.of((() => other.run()(this.run())))) } run() { diff --git a/src/algebra/list.js b/src/algebra/list.js index bc84dae..ec6da93 100644 --- a/src/algebra/list.js +++ b/src/algebra/list.js @@ -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') - -/** - * @template T - * @extends {Pure} - */ -class ListPure extends AlgebraWithBase(Pure)(Category, Foldable, Monoid) { - static empty() { - return List.empty() - } - - static id() { - return List.empty() - } - - head() { - return this._value - } - - tail() { - return Empty - } - - /** - * @template U - * @this {ListPure} - * @param {Morphism>} f - * @returns {ListPure} - */ - chain(f) { - return f(this.head()) - } - - /** - * @template U - * @this {ListPure} - * @param {Morphism} f - * @returns {ListPure} - */ - 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} - * @returns {this is Empty} - */ - isEmpty() { - return this === Empty - } -} - -/** - * @template T - * @extends {Suspend} - */ -class ListSuspend extends AlgebraWithBase(Suspend)(Foldable, Monoid, Comonad) { - /** - * @param {T} head - * @param {() => List} tail - */ - constructor(head, tail) { - super( - head, - /** @type {InferredMorphism} */(tail) - ) - } - - - static empty() { - return List.empty() +class Empty extends Algebra(Semigroup, Foldable, Monoid, Comonad) { + constructor() { + super() } /** * @template T - * @this {ListSuspend} + * @this {Empty} * @param {T} value - * @returns {ListSuspend} + * @returns {List} */ - cons(value) { - return new ListSuspend(value, () => this) + prepend(value) { + return new Element(value, () => this) } head() { - return this._value + return empty } tail() { - return this.step() + return empty + } + + /** + * @template T + * @param {List>} other + * @returns {List>} + */ + 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} tail + */ + constructor(head, tail = List.empty) { + super() + this.#head = head + this.#tail = tail + } + + /** + * @template T + * @this {Empty} + * @param {T} value + * @returns {List} + */ + prepend(value) { + return new Element(value, () => this) + } + + head() { + return this.#head + } + + tail() { + return this.#tail() + } + + /** + * @param {List} other + * @returns {List} + */ + concat(other) { + if (this.isEmpty()) { + return other + } else { + return new Element( + this.head(), + () => this.tail().concat(other) + ) + } + } + + /** + * @template U + * @param {Morphism>} f + * @returns {List} + */ + 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 | ListSuspend} List + * @type {Element | Empty} */ export class List { /** @@ -145,7 +143,7 @@ export class List { * @returns {List} */ 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} tail + * @returns {List} + */ + 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) +