diff --git a/src/algebra/free.js b/src/algebra/free.js
index 9bf132d..536ed01 100644
--- a/src/algebra/free.js
+++ b/src/algebra/free.js
@@ -1,145 +1,145 @@
-import { id, map, ap, prepend, reduce, thunk } from '../../vendor/izuna/src/index.js'
-/** @import { Morphism } from './types.js' */
+import { Algebra, Monad } from './interfaces.js'
+import { curry } from '../..//vendor/izuna/src/index.js'
+/** @import { Applicative, ApplicativeTypeRef, Morphism } from './types.js' */
+
+
+/** @import { InferredMorphism } from './types.js' */
+
+const kleisli = curry((f, g, x) => f(x).chain(g))
/**
- * @template A
- * @typedef {Pure | Impure} Free
+ * @template T
+ * @type {Pure | Impure} Free
*/
+export class Free extends Algebra(Monad) {
+ /**
+ * @template T
+ * @param {T} value
+ */
+ static of(value) {
+ return new Pure(value)
+ }
+}
-/** @template A */
-export class Pure {
+/** @template T */
+export class Pure extends Free {
#value
/**
- * @param {A} value
+ * @param {T} value
*/
constructor(value) {
+ super()
this.#value = value
}
/**
- * @template {Free} B
- * @param {Morphism} f
- * @returns {B}
+ * @param {InferredMorphism} f
*/
chain(f) {
return f(this.#value)
}
/**
- * @template B
- * @param {Morphism} f
- * @returns {Free}
+ * @param {InferredMorphism} f
*/
map(f) {
- // @ts-ignore
return pure(f(this.#value))
}
/**
- * @template B, C
- * @param {Free} b
- * @returns {Free}
+ * @template U
+ * @template {Applicative} M
+ * @template {ApplicativeTypeRef} TypeRef
+ * @param {TypeRef} _A
+ * @param {*} f
+ * @returns {M}
*/
- ap(b) {
- return b.map(this.#value)
+ traverse(_A, f) {
+ return f(this.#value).map(pure)
}
- traverse(f, of) {
- return of(this.map(f))
+ run() {
+ return this.#value
}
-
- /**
- * @template B
- * @param {(acc: B, value: A) => B} f
- * @param {B} init
- * @returns {B}
- */
- reduce(f, init) {
- return f(init, this.#value)
- }
-
}
-/**
- * @template A
- * @typedef {(value: A) => Free} Computation
- */
-
-/** @template A */
-export class Impure {
+/** @template T */
+export class Impure extends Free {
+ #value
#next
/**
- * @param {() => Free} next
+ * @param {T} value
+ * @param {(value: T) => Free} f
*/
- constructor(next) {
- this.#next = next
+ constructor(value, f) {
+ super()
+ this.#value = value
+ this.#next = f
}
/**
- * @template {Free} B
- * @param {Morphism} f
- * @returns {B}
+ * @param {(value: T) => Free} f
*/
chain(f) {
- // @ts-ignore
- return new Impure(
- // @ts-ignore
- () => this.#next().chain(f)
- )
+ return impure(this.#value, kleisli(this.#next, f))
}
- /**
- * @template B
- * @param {Morphism} f
- * @returns {Free}
- */
map(f) {
- return new Impure(
- // @ts-ignore
- () => this.#next().map(f)
+ return impure(
+ this.#value,
+ y => f(y).chain(f)
)
}
/**
- * @template B
- * @param {Free} b
- * @returns {Free}
+ * @template U
+ * @template {Applicative} M
+ * @template {ApplicativeTypeRef} TypeRef
+ * @param {TypeRef} A
+ * @param {(value: T) => M} f
+ * @returns {M}
*/
- ap(b) {
- return new Impure(
- // @ts-ignore
- () => b.map(this.#next())
+ traverse(A, f) {
+ const fb = f(this.#value)
+ const rest = this.#next(this.#value).traverse(A, f)
+
+ return rest.ap(
+ fb.map(b => next => impure(b, () => next))
)
}
- traverse(f, of) {
- return of(liftF(
- this.#next().traverse(f, of)
- ))
- }
-
- /**
- * @template B
- * @param {(acc: B, value: A) => B} f
- * @param {B} acc
- * @returns {B}
- */
- reduce(f, acc) {
-
+ run() {
+ return this.#next(this.#value).run()
}
}
-const sequence = (of, iter) => {
- return reduce((acc, x) => {
- ap(acc, map(prepend, x))
- }, of([]), iter)
-}
+/**
+ * @template T
+ * @param {T} x
+ * @returns {Pure}
+ */
+const pure = x => new Pure(x)
-export const pure = x => new Pure(x)
-export const impure = f => new Impure(f)
+/**
+ * @template T
+ * @param {T} x
+ * @param {(value: T) => Free} f
+ * @returns {Impure}
+ */
+const impure = (x, f) => new Impure(x, f)
-export const liftF = effect => impure(thunk(effect))
+/**
+ * @template T
+ * @param {T} x
+ * @returns Impure
+ */
+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())
diff --git a/src/algebra/identity.js b/src/algebra/identity.js
new file mode 100644
index 0000000..d892b41
--- /dev/null
+++ b/src/algebra/identity.js
@@ -0,0 +1,65 @@
+import { Algebra, Comonad, Monad } from './interfaces.js'
+
+/** @import { Morphism } from './types.js' */
+
+/** @template T */
+export class Identity extends Algebra(Monad, Comonad) {
+ #value
+
+ /**
+ * @param {T} value
+ */
+ constructor(value) {
+ super()
+
+ this.#value = value
+ }
+
+ /**
+ * @template T
+ * @param {T} value
+ */
+ static of(value) {
+ return id(value)
+ }
+
+ /**
+ * @template U
+ * @param {Morphism} f
+ */
+ map(f) {
+ return id(f(this.#value))
+ }
+
+ /**
+ * @template U
+ * @this {Identity>}
+ * @param {Identity} b
+ * @returns {Identity}
+ */
+ ap(b) {
+ return b.map(this.#value)
+ }
+
+ /**
+ * @template U
+ * @param {Morphism>} f
+ */
+ chain(f) {
+ return f(this.#value)
+ }
+
+ /**
+ * @param {(value: Identity) => T} f
+ */
+ extend(f) {
+ return id(f(this))
+ }
+
+ extract() {
+ return this.#value
+ }
+}
+
+export const id = value => new Identity(value)
+
diff --git a/src/algebra/interfaces.js b/src/algebra/interfaces.js
index 08b6b9d..f877198 100644
--- a/src/algebra/interfaces.js
+++ b/src/algebra/interfaces.js
@@ -1,4 +1,4 @@
-import { mix } from '../mixin.js'
+import { mix, Mixin } from '../mixin.js'
/** @import { MixinFunction } from '../mixin.js' */
/** @import { Fn } from './types.js' */
@@ -130,7 +130,7 @@ class Method {
*/
_getInstallationPoint(target) {
switch (this.#type) {
- case 0: return target.prototype
+ case 0: return Object.getPrototypeOf(target)
case 1: return target
default: return target
}
@@ -152,6 +152,9 @@ const Implementations = Symbol()
class Interface {
#name
+ /** @type {MixinFunction} */
+ #mixin
+
/** @type {Set} */
#methods = new Set()
@@ -161,20 +164,20 @@ class Interface {
/** @param {string} name */
constructor(name) {
this.#name = name
+ this.#mixin = Mixin(this.build.bind(this))
}
get name() {
return this.#name
}
- findInterfaces() {
+ findInterfaces(type) {
let interfaces = new Set()
- let current = Object.getPrototypeOf(this)
- console.log(current)
+ let current = type
while (current != null) {
- interfaces = new Set(current[Implementations])
- current = Object.getPrototypeOf(this)
+ interfaces = new Set(current[Implementations]).union(interfaces)
+ current = Object.getPrototypeOf(current)
}
return interfaces
@@ -185,7 +188,7 @@ class Interface {
* @returns {boolean}
*/
implementedBy(type) {
- return this.findInterfaces().has(type)
+ return this.findInterfaces(type).has(this)
}
/**
@@ -212,10 +215,10 @@ class Interface {
}
/**
- * @returns {MixinWrapper}
+ * @returns {MixinFunction}
*/
- intoWrapper() {
- return this.build.bind(this)
+ asMixin() {
+ return this.#mixin
}
/**
@@ -223,19 +226,20 @@ class Interface {
*/
build(base) {
const interfaces = [...this.#interfaces.values()]
- const wrappers = interfaces.map(x => x.intoWrapper())
+ const mixins = interfaces.map(x => x.asMixin())
- const Interfaces = mix(base).with(...wrappers)
+ const Interfaces = mix(base).with(...mixins)
- //const Algebra = class extends Interfaces { }
+ const Algebra = class extends Interfaces { }
for (const method of this.#methods) {
- method.implement(this, Interfaces)
+ method.implement(this, Algebra)
}
- Interfaces.prototype[Implementations] = new Set([...interfaces])
+ const prototype = Object.getPrototypeOf(Algebra)
+ prototype[Implementations] = new Set(interfaces)
- return Interfaces
+ return Algebra
}
}
@@ -272,7 +276,7 @@ export const apply = (methodName, f, obj) => {
* @returns {(...algebras: Interface[]) => FunctionConstructor}
*/
export const AlgebraWithBase = base => (...algebras) => {
- return mix(base).with(...algebras.map(x => x.intoWrapper()))
+ return mix(base).with(...algebras.map(x => x.asMixin()))
}
/**
diff --git a/src/algebra/option.js b/src/algebra/option.js
index 945431d..60d8a21 100644
--- a/src/algebra/option.js
+++ b/src/algebra/option.js
@@ -2,8 +2,22 @@ import { Algebra, Foldable, Monad, Monoid, Setoid } from './interfaces.js'
/** @import { Morphism } from './types.js' */
+/**
+ * @template T
+ * @type {Some | None} Option
+ */
+export class Option extends Algebra(Setoid, Monad, Foldable) {
+ /**
+ * @template T
+ * @param {T} value
+ */
+ static of(value) {
+ return Some.of(value)
+ }
+}
+
/** @template T */
-export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) {
+export class Some extends Option {
/** @type {T} */
#value
@@ -23,11 +37,18 @@ export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) {
return new Some(value)
}
+ /**
+ * @param {Option} other
+ */
equals(other) {
return other instanceof Some &&
other.chain(v => v === this.#value)
}
+ ap(b) {
+ return b.chain(f => this.map(f))
+ }
+
/**
* @template R
* @param {Morphism} f
@@ -64,18 +85,21 @@ export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) {
reduce(f, init) {
return f(init, this.#value)
}
-
- empty() {
- return Option.empty()
- }
}
/** @template T */
-export class None extends Algebra(Setoid, Monoid, Monad, Foldable) {
+export class None extends Option {
+ /**
+ * @param {Option} other
+ */
equals(other) {
return other === none
}
+ ap(_b) {
+ return this
+ }
+
/**
* @template R
* @param {Morphism} _f
@@ -118,24 +142,6 @@ export class None extends Algebra(Setoid, Monoid, Monad, Foldable) {
}
}
-/**
- * @template T
- * @type {Some | None} Option
- */
-export class Option {
- /**
- * @template T
- * @param {T} value
- */
- static of(value) {
- return Some.of(value)
- }
-
- static empty() {
- return none
- }
-}
-
/**
* @template T
* @param {T} value
diff --git a/src/algebra/result.js b/src/algebra/result.js
index aef2458..da59344 100644
--- a/src/algebra/result.js
+++ b/src/algebra/result.js
@@ -1,11 +1,26 @@
-import { Algebra, Foldable, Functor, Monad, Setoid } from './interfaces.js'
+import { Algebra, Foldable, Monad, Setoid } from './interfaces.js'
/** @import { Morphism } from './types.js' */
/**
* @template T, E
+ * @type {Ok | Err} Result
*/
-export class Ok extends Algebra(Setoid, Monad, Foldable) {
+export class Result extends Algebra(Setoid, Monad, Foldable) {
+ /**
+ * @template T
+ * @param {T} value
+ * @returns {Ok}
+ */
+ static of(value) {
+ return new Ok(value)
+ }
+}
+
+/**
+ * @template T, E
+ */
+export class Ok extends Result {
/** @type {T} */
#value
@@ -20,7 +35,7 @@ export class Ok extends Algebra(Setoid, Monad, Foldable) {
}
/**
- * @param {Ok} other
+ * @param {Result} other
*/
equals(other) {
return other instanceof Ok &&
@@ -86,7 +101,7 @@ export class Ok extends Algebra(Setoid, Monad, Foldable) {
/**
* @template T, E
*/
-export class Err extends Algebra(Setoid, Functor, Monad) {
+export class Err extends Result {
/** @type {E} */
#value
@@ -100,6 +115,9 @@ export class Err extends Algebra(Setoid, Functor, Monad) {
this.#value = value
}
+ /**
+ * @param {Result} other
+ */
equals(other) {
return other instanceof Err &&
other.chain(v => v === this.#value)
@@ -158,21 +176,6 @@ export class Err extends Algebra(Setoid, Functor, Monad) {
}
}
-/**
- * @template T, E
- * @type {Ok | Err} Result
- */
-export class Result {
- /**
- * @template T
- * @param {T} value
- * @returns {Ok}
- */
- static of(value) {
- return new Ok(value)
- }
-}
-
/**
* @template T, E
* @param {T} v
diff --git a/src/algebra/types.js b/src/algebra/types.js
index a3690c7..d8b95d6 100644
--- a/src/algebra/types.js
+++ b/src/algebra/types.js
@@ -17,17 +17,197 @@ export default {}
/**
* @template T
- * @typedef {(value: T) => Chain} ChainConstructor
+ * @typedef {{
+ equals: (b: Setoid) => boolean
+ * }} Setoid
*/
/**
* @template T
- * @typedef {(f: Morphism) => R} chain
+ * @typedef {{
+ lte: (b: Ord) => boolean
+ * }} Ord
+ */
+
+/**
+ * @template T, U
+ * @typedef {*} Semigroupoid
+ */
+
+/**
+ * @template I, J
+ * @typedef {{
+ compose: (b: Semigroupoid) => Semigroupoid
+ * }} Category
+ */
+
+/**
+ * @template T, U
+ * @template {Category} M
+ * @typedef {{
+ id: () => (value: T) => M
+ * }} CategoryTypeRef
*/
/**
* @template T
- * @typedef {{ chain: chain }} Chain
+ * @typedef {{
+ concat: (b: Semigroup) => Semigroup
+ * }} Semigroup
+ */
+
+/**
+ * @template T
+ * @typedef{Semigroup} Monoid
+ */
+
+/**
+ * @template T
+ * @template {Monoid} M
+ * @typedef {{
+ empty: () => M
+ * }} MonoidTypeRef
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Monoid &
+ { invert: () => Group }
+ * } Group
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ filter: (f: (acc: U, val: T) => U, init: U) => U
+ * }} Filterable
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ map: (f: Morphism) => Functor
+ * }} Functor
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ contramap: (f: Morphism) => Contravariant
+ * }} Contravariant
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ ap: (f: Apply>) => Apply
+ * }} Apply
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ ap: (b: Applicative>) => Applicative,
+ map: (f: Morphism) => Applicative
+ * }} Applicative
+ */
+
+/**
+ * @template T
+ * @template {Applicative} M
+ * @typedef {{
+ of: (value: T) => M
+ * }} ApplicativeTypeRef
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Functor &
+ { alt: (b: Alt) => Alt }
+ * } Alt
+ */
+
+/**
+ * @template T
+ * @typedef {Alt} Plus
+ */
+
+/**
+ * @template T
+ * @template {Plus} M
+ * @typedef {
+ Alt &
+ { zero: () => M }
+ * } PlusTypeRef
+ */
+
+/**
+ * @template T
+ * @typedef {Applicative & Plus} Alternative
+ */
+
+/**
+ * @template T
+ * @template {Applicative & Plus} M
+ * @typedef {ApplicativeTypeRef & PlusTypeRef} AlternativeTypeRef
+ */
+
+/**
+ * @template T
+ * @typedef {{
+ filter: (f: (acc: U, val: T) => U, init: U) => U
+ * }} Foldable
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Functor & Foldable &
+ { traverse: (A: ApplicativeTypeRef>, f: (val: T) => Applicative) => Applicative> }
+ * } Traversable
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Apply &
+ { chain: (f: (value: T) => Chain) => Chain }
+ * } Chain
+ */
+
+/**
+ * @template T
+ * @typedef {Chain} ChainRec
+ */
+
+/**
+ * @template T
+ * @typedef {Functor & Applicative & Chain} Monad
+ */
+
+/**
+ * @template T
+ * @template {Monad} M
+ * @typedef {ApplicativeTypeRef} MonadTypeDef
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Functor &
+ { extend: (f: (val: Extend) => U) => Extend}
+ * } Extend
+ */
+
+/**
+ * @template T
+ * @typedef {
+ Extend &
+ { extract: () => T }
+ * } Comonad
*/
/** @typedef {(...args: any[]) => any} Fn */
+
diff --git a/src/mixin.js b/src/mixin.js
index 89fbe27..31fe6ad 100644
--- a/src/mixin.js
+++ b/src/mixin.js
@@ -246,7 +246,7 @@ class MixinBuilder {
/**
* Applies `mixins` in order to the superclass given to `mix()`.
*
- * @param {Array.} mixins
+ * @param {Array.} mixins
* @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied
*/
with(...mixins) {
diff --git a/tests/units/monad.js b/tests/units/monad.js
index 6ce7ed4..c335d5f 100644
--- a/tests/units/monad.js
+++ b/tests/units/monad.js
@@ -1,28 +1,29 @@
// @ts-nocheck
import { it, assert } from 'folktest'
-import { Option, Result } from '../../src/index.js'
-import { Chain, Ord, Setoid } from '../../src/algebra/interfaces.js'
+import { IO, List, Option, Reader, Result, Some } from '../../src/index.js'
+import { Applicative, Chain, Ord, Setoid } from '../../src/algebra/interfaces.js'
-const Algebra = [Option, Result]
+const Algebra = [Option, Result, List, IO, Reader]
-const inc = x => F.of(x + 1)
-const dbl = x => F.of(x * 2)
+const inc = F => x => F.of(x + 1)
+const dbl = F => x => F.of(x * 2)
const impl = i => m => i.implementedBy(m)
export const Tests = [
it('unit is a left identity', () => {
- console.log(Chain.implementedBy(Result))
- Algebra.filter(impl(Chain)).forEach(algebra => {
+ Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
+ const f = inc(algebra)
+
assert(
- algebra.of(1).chain(inc) == inc(1),
+ algebra.of(1).chain(f).equals(f(1)),
`${algebra.name} is not a left identity`
)
})
}),
it('unit is a right identity', () => {
- Algebra.filter(impl(Chain)).forEach(algebra => {
+ Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
const m = algebra.of(1)
assert(
@@ -33,36 +34,15 @@ export const Tests = [
}),
it('unit is associative', () => {
- Algebra.filter(impl(Chain)).forEach(algebra => {
+ Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
const a = algebra.of(1)
+ const f = inc(algebra)
+ const g = dbl(algebra)
- const x = a.chain(inc).chain(dbl)
- const y = a.chain(x => inc(x).chain(dbl))
+ const x = a.chain(f).chain(g)
+ const y = a.chain(x => f(x).chain(g))
- assert(x == y, `${algebra.name} is not associative`)
+ assert(x.equals(y), `${algebra.name} is not associative`)
})
}),
-
- it('unit is reflexive', () => {
- Algebra.filter(impl(Setoid)).forEach(algebra => {
- const a = algebra.of(1)
- assert(a.equals(a), `${algebra.name} is not reflexive`)
- })
- }),
-
- it('unit is symmetrical', () => {
- Algebra.filter(impl(Setoid)).forEach(algebra => {
- const a = algebra.of(1)
- const b = algebra.of(1)
- assert(a.equals(b) && b.equals(a), `${algebra.name} is not symmetrical`)
- })
- }),
-
- it('unit is antisymmetrical', () => {
- Algebra.filter(impl(Ord)).forEach(algebra => {
- const a = algebra.of(1)
- const b = algebra.of(1)
- assert(a.lte(b) && b.lte(a) && a.equals(b))
- })
- })
]