diff --git a/jsconfig.json b/jsconfig.json index 6ec80e6..4f05d0e 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "module": "es2020", "target": "es6", - "lib": ["es2022", "dom"], + "lib": ["esnext", "dom"], "checkJs": true, "paths": { "/*": ["./*"] diff --git a/src/algebra/fn.js b/src/algebra/fn.js index ec38801..a3c894b 100644 --- a/src/algebra/fn.js +++ b/src/algebra/fn.js @@ -1,5 +1,5 @@ import { curry } from '../curry.js' -import { Free, Suspend, pure } from './free.js' +import { Free, Suspend, suspend, pure } from './free.js' /** @import {InferredMorphism, Morphism, ChainConstructor} from './types.js' */ @@ -35,9 +35,9 @@ export const kleisliCompose = curry( /** * @template T * @param {T} x - * @returns {Free} + * @returns {Suspend} */ -export const liftF = x => new Suspend( +export const liftF = x => suspend( x, /** @type {InferredMorphism} */(pure) ) diff --git a/src/algebra/free.js b/src/algebra/free.js index 80b75fb..bb68f07 100644 --- a/src/algebra/free.js +++ b/src/algebra/free.js @@ -1,11 +1,15 @@ import { liftF } from './fn.js' -import { Algebra, Comonad, EitherOf, Functor, Monad } from './index.js' +import { Algebra, BaseSet, Comonad, Monad } from './index.js' /** @import { InferredMorphism, Morphism } from './types.js' */ -/** @template T */ -export class Suspend extends Algebra(Functor, Monad) { - #value +/** + * @template T + * @extends BaseSet + * @mixes Monad + */ +export class Suspend extends Algebra(Monad) { + _value #fn /** @@ -14,7 +18,7 @@ export class Suspend extends Algebra(Functor, Monad) { */ constructor(value, fn) { super() - this.#value = value + this._value = value this.#fn = fn } @@ -30,7 +34,7 @@ export class Suspend extends Algebra(Functor, Monad) { * @returns {Suspend} */ chain(f) { - return new Suspend(this.#value, x => this.#fn(x).chain(f)) + return new Suspend(this._value, x => this.#fn(x).chain(f)) } /** @@ -39,7 +43,7 @@ export class Suspend extends Algebra(Functor, Monad) { * @returns {Suspend} */ map(g) { - return new Suspend(this.#value, x => this.#fn(x).map(g)) + return new Suspend(this._value, x => this.#fn(x).map(g)) } /** @@ -51,19 +55,23 @@ export class Suspend extends Algebra(Functor, Monad) { return this.map(g) } + step() { + return this.#fn(this._value) + } + run() { - return this.#fn(this.#value) + return this.step().run() } } /** @template T */ -export class Pure extends Algebra(Functor, Monad, Comonad) { - #value +export class Pure extends Algebra(Monad, Comonad) { + _value /** @param {T} value */ constructor(value) { super() - this.#value = value + this._value = value } /** @returns {this is Pure} */ @@ -78,7 +86,7 @@ export class Pure extends Algebra(Functor, Monad, Comonad) { * @returns {Pure} */ chain(f) { - return f(this.#value) + return f(this._value) } /** @@ -100,11 +108,15 @@ export class Pure extends Algebra(Functor, Monad, Comonad) { } extract() { - return this.#value + return this._value + } + + step() { + return this._value } run() { - return this.#value + return this._value } } diff --git a/src/algebra/index.js b/src/algebra/index.js index c60e39b..37c71a1 100644 --- a/src/algebra/index.js +++ b/src/algebra/index.js @@ -214,14 +214,8 @@ class Interface { } } -/** @template T*/ -class BaseSet { - _value - - /** @param {T} value */ - constructor(value) { - this._value = value - } +/** @template T */ +export class BaseSet { } /** @@ -250,13 +244,19 @@ export const apply = (methodName, f, obj) => { } } +/** + * @param {Function} base + * @returns {(...algebras: Interface[]) => FunctionConstructor} + */ +export const AlgebraWithBase = base => (...algebras) => { + return mix(base).with(...algebras.map(x => x.intoWrapper())) +} + /** * @param {...Interface} algebras * @returns {FunctionConstructor} */ -export const Algebra = (...algebras) => { - return mix(BaseSet).with(...algebras.map(x => x.intoWrapper())) -} +export const Algebra = AlgebraWithBase(BaseSet) export const Setoid = new Interface('Setoid') .specifies('equals') @@ -331,7 +331,7 @@ export const Chain = new Interface('Chain') Method.from('chain') .implementation(function(f) { return f(this._value) - })) + }) ) export const ChainRef = new Interface('ChainRec') diff --git a/src/algebra/list.js b/src/algebra/list.js index 9246a17..ddb9078 100644 --- a/src/algebra/list.js +++ b/src/algebra/list.js @@ -1,54 +1,110 @@ import { liftF } from './fn.js' -import { Free } from './free.js' -import { Algebra, Foldable, Functor, Monad } from './index.js' -import { none as None } from './option.js' +import { Pure, Suspend } from './free.js' +import { AlgebraWithBase, Comonad, Foldable } from './index.js' -/** @template T */ -export class List extends Algebra(Functor, Monad, Foldable) { - /** @type {Free | None} */ - _head +/** @import { InferredMorphism } from './types.js' */ - /** @type {List} */ - _tail +const Nil = Symbol('Nil') - /** - * @param {T | None} [head] - * @param {List} [tail=List] - */ - constructor(head = None, tail = List.of(None)) { - super() - this._head = head - this._tail = tail +/** + * @template T + * @extends {Pure} + */ +class ListPure extends AlgebraWithBase(Pure)(Foldable) { + head() { + return this.head + } + + tail() { + return Empty } /** - * @template T - * @param {T} value - * @returns {List} + * @template U + * @param {(acc: U, value: T) => U} f + * @param {U} init + * @returns {U} */ - prepend(value) { - return new List(value, this) - } - - /** - * @template T - * @param {T} value - * @returns {List} - */ - static of(value) { - return new List(value) - } - - /** - * @template T - * @param {Iterable} iterable - * @returns {List} - */ - static from(iterable) { - Array.from(iterable).reduceRight((list, value)) + reduce(f, init) { + return f(init, this._value) } } -const list = liftF(1) -console.log(list.map(_ => 2).map(_ => 3).chain(x => x).run()) + +/** + * @template T + * @extends {Suspend} + */ +class ListSuspend extends AlgebraWithBase(Suspend)(Foldable, Comonad) { + /** + * @param {IteratorObject} iter + */ + constructor(iter) { + const next = iter.next() + const value = /** @type {T} */ (next.value) + + const fn = /** @type {InferredMorphism} */ (next.done ? List.empty : () => new ListSuspend(iter)) + super(value, fn) + } + + head() { + return this._value + } + + tail() { + return this.step() + } + + /** + * @template U + * @param {(acc: U, value: T) => U} f + * @param {U} init + * @returns {U} + */ + reduce(f, init) { + const acc = f(init, this._value) + return this.tail().reduce(f, acc) + } + + extract() { + return this.reduce((acc, value) => acc.concat(value), []) + } +} + +/** + * @template T + * @type {ListPure | ListSuspend} List + */ +export class List { + /** + * @template {Iterable} T + * @param {T} value + * @returns {ListSuspend} + */ + static of(value) { + // @ts-ignore + return new ListSuspend(liftF(value)) + } + + /** + * @template T + * @param {Iterable} iterator + * @returns {ListSuspend} + */ + static from(iterator) { + return new ListSuspend(Iterator.from(iterator)) + } + + static empty() { + return Empty + } +} + +const Empty = new ListPure(Nil) + + +const a = Array.from({ length: 100 }).map((_, i) => i) + +const wawa = List.from(a) +console.log(wawa.reduce((acc, value) => acc.concat(value), []))