wip types

This commit is contained in:
Rowan 2025-04-11 00:36:41 -05:00
parent 1e09ca655f
commit 853601f2c5
8 changed files with 426 additions and 188 deletions

View file

@ -1,145 +1,145 @@
import { id, map, ap, prepend, reduce, thunk } from '../../vendor/izuna/src/index.js' import { Algebra, Monad } from './interfaces.js'
/** @import { Morphism } from './types.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 * @template T
* @typedef {Pure<A> | Impure<A>} Free * @type {Pure<T> | Impure<T>} Free
*/ */
export class Free extends Algebra(Monad) {
/**
* @template T
* @param {T} value
*/
static of(value) {
return new Pure(value)
}
}
/** @template A */ /** @template T */
export class Pure { export class Pure extends Free {
#value #value
/** /**
* @param {A} value * @param {T} value
*/ */
constructor(value) { constructor(value) {
super()
this.#value = value this.#value = value
} }
/** /**
* @template {Free<B>} B * @param {InferredMorphism<T>} f
* @param {Morphism<A, B>} f
* @returns {B}
*/ */
chain(f) { chain(f) {
return f(this.#value) return f(this.#value)
} }
/** /**
* @template B * @param {InferredMorphism<T>} f
* @param {Morphism<A, B>} f
* @returns {Free<B>}
*/ */
map(f) { map(f) {
// @ts-ignore
return pure(f(this.#value)) return pure(f(this.#value))
} }
/** /**
* @template B, C * @template U
* @param {Free<B>} b * @template {Applicative<U>} M
* @returns {Free<C>} * @template {ApplicativeTypeRef<U, M>} TypeRef
* @param {TypeRef} _A
* @param {*} f
* @returns {M}
*/ */
ap(b) { traverse(_A, f) {
return b.map(this.#value) return f(this.#value).map(pure)
} }
traverse(f, of) { run() {
return of(this.map(f)) 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 T */
* @template A export class Impure extends Free {
* @typedef {<B>(value: A) => Free<B>} Computation #value
*/
/** @template A */
export class Impure {
#next #next
/** /**
* @param {() => Free<any>} next * @param {T} value
* @param {(value: T) => Free<any>} f
*/ */
constructor(next) { constructor(value, f) {
this.#next = next super()
this.#value = value
this.#next = f
} }
/** /**
* @template {Free<B>} B * @param {<U>(value: T) => Free<U>} f
* @param {Morphism<A, B>} f
* @returns {B}
*/ */
chain(f) { chain(f) {
// @ts-ignore return impure(this.#value, kleisli(this.#next, f))
return new Impure(
// @ts-ignore
() => this.#next().chain(f)
)
} }
/**
* @template B
* @param {Morphism<A, B>} f
* @returns {Free<B>}
*/
map(f) { map(f) {
return new Impure( return impure(
// @ts-ignore this.#value,
() => this.#next().map(f) y => f(y).chain(f)
) )
} }
/** /**
* @template B * @template U
* @param {Free<any>} b * @template {Applicative<U>} M
* @returns {Free<B>} * @template {ApplicativeTypeRef<U, M>} TypeRef
* @param {TypeRef} A
* @param {(value: T) => M} f
* @returns {M}
*/ */
ap(b) { traverse(A, f) {
return new Impure( const fb = f(this.#value)
// @ts-ignore const rest = this.#next(this.#value).traverse(A, f)
() => b.map(this.#next())
return rest.ap(
fb.map(b => next => impure(b, () => next))
) )
} }
traverse(f, of) { run() {
return of(liftF( return this.#next(this.#value).run()
this.#next().traverse(f, of)
))
} }
}
/** /**
* @template B * @template T
* @param {(acc: B, value: A) => B} f * @param {T} x
* @param {B} acc * @returns {Pure<T>}
* @returns {B}
*/ */
reduce(f, acc) { const pure = x => new Pure(x)
} /**
} * @template T
* @param {T} x
* @param {(value: T) => Free<any>} f
* @returns {Impure<T>}
*/
const impure = (x, f) => new Impure(x, f)
const sequence = (of, iter) => { /**
return reduce((acc, x) => { * @template T
ap(acc, map(prepend, x)) * @param {T} x
}, of([]), iter) * @returns Impure<T>
} */
export const liftF = x => impure(x, pure)
import { id, Identity } from './identity.js'
import { Option, some } from './option.js'
export const pure = x => new Pure(x) const a = liftF(1).chain(() => liftF(2)).traverse(Option, x => some(1))
export const impure = f => new Impure(f) console.log(a.chain(x => x).run())
export const liftF = effect => impure(thunk(effect))

65
src/algebra/identity.js Normal file
View file

@ -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<T, U>} f
*/
map(f) {
return id(f(this.#value))
}
/**
* @template U
* @this {Identity<Morphism<U, T>>}
* @param {Identity<U>} b
* @returns {Identity<T>}
*/
ap(b) {
return b.map(this.#value)
}
/**
* @template U
* @param {Morphism<T, Identity<U>>} f
*/
chain(f) {
return f(this.#value)
}
/**
* @param {(value: Identity<T>) => T} f
*/
extend(f) {
return id(f(this))
}
extract() {
return this.#value
}
}
export const id = value => new Identity(value)

View file

@ -1,4 +1,4 @@
import { mix } from '../mixin.js' import { mix, Mixin } from '../mixin.js'
/** @import { MixinFunction } from '../mixin.js' */ /** @import { MixinFunction } from '../mixin.js' */
/** @import { Fn } from './types.js' */ /** @import { Fn } from './types.js' */
@ -130,7 +130,7 @@ class Method {
*/ */
_getInstallationPoint(target) { _getInstallationPoint(target) {
switch (this.#type) { switch (this.#type) {
case 0: return target.prototype case 0: return Object.getPrototypeOf(target)
case 1: return target case 1: return target
default: return target default: return target
} }
@ -152,6 +152,9 @@ const Implementations = Symbol()
class Interface { class Interface {
#name #name
/** @type {MixinFunction} */
#mixin
/** @type {Set<Method>} */ /** @type {Set<Method>} */
#methods = new Set() #methods = new Set()
@ -161,20 +164,20 @@ class Interface {
/** @param {string} name */ /** @param {string} name */
constructor(name) { constructor(name) {
this.#name = name this.#name = name
this.#mixin = Mixin(this.build.bind(this))
} }
get name() { get name() {
return this.#name return this.#name
} }
findInterfaces() { findInterfaces(type) {
let interfaces = new Set() let interfaces = new Set()
let current = Object.getPrototypeOf(this) let current = type
console.log(current)
while (current != null) { while (current != null) {
interfaces = new Set(current[Implementations]) interfaces = new Set(current[Implementations]).union(interfaces)
current = Object.getPrototypeOf(this) current = Object.getPrototypeOf(current)
} }
return interfaces return interfaces
@ -185,7 +188,7 @@ class Interface {
* @returns {boolean} * @returns {boolean}
*/ */
implementedBy(type) { implementedBy(type) {
return this.findInterfaces().has(type) return this.findInterfaces(type).has(this)
} }
/** /**
@ -212,10 +215,10 @@ class Interface {
} }
/** /**
* @returns {MixinWrapper} * @returns {MixinFunction}
*/ */
intoWrapper() { asMixin() {
return this.build.bind(this) return this.#mixin
} }
/** /**
@ -223,19 +226,20 @@ class Interface {
*/ */
build(base) { build(base) {
const interfaces = [...this.#interfaces.values()] 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) { 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} * @returns {(...algebras: Interface[]) => FunctionConstructor}
*/ */
export const AlgebraWithBase = base => (...algebras) => { export const AlgebraWithBase = base => (...algebras) => {
return mix(base).with(...algebras.map(x => x.intoWrapper())) return mix(base).with(...algebras.map(x => x.asMixin()))
} }
/** /**

View file

@ -2,8 +2,22 @@ import { Algebra, Foldable, Monad, Monoid, Setoid } from './interfaces.js'
/** @import { Morphism } from './types.js' */ /** @import { Morphism } from './types.js' */
/**
* @template T
* @type {Some<T> | None<T>} Option
*/
export class Option extends Algebra(Setoid, Monad, Foldable) {
/**
* @template T
* @param {T} value
*/
static of(value) {
return Some.of(value)
}
}
/** @template T */ /** @template T */
export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) { export class Some extends Option {
/** @type {T} */ /** @type {T} */
#value #value
@ -23,11 +37,18 @@ export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) {
return new Some(value) return new Some(value)
} }
/**
* @param {Option<T>} other
*/
equals(other) { equals(other) {
return other instanceof Some && return other instanceof Some &&
other.chain(v => v === this.#value) other.chain(v => v === this.#value)
} }
ap(b) {
return b.chain(f => this.map(f))
}
/** /**
* @template R * @template R
* @param {Morphism<T, R>} f * @param {Morphism<T, R>} f
@ -64,18 +85,21 @@ export class Some extends Algebra(Setoid, Monoid, Monad, Foldable) {
reduce(f, init) { reduce(f, init) {
return f(init, this.#value) return f(init, this.#value)
} }
empty() {
return Option.empty()
}
} }
/** @template T */ /** @template T */
export class None extends Algebra(Setoid, Monoid, Monad, Foldable) { export class None extends Option {
/**
* @param {Option<T>} other
*/
equals(other) { equals(other) {
return other === none return other === none
} }
ap(_b) {
return this
}
/** /**
* @template R * @template R
* @param {Morphism<T, R>} _f * @param {Morphism<T, R>} _f
@ -118,24 +142,6 @@ export class None extends Algebra(Setoid, Monoid, Monad, Foldable) {
} }
} }
/**
* @template T
* @type {Some<T> | None<T>} Option
*/
export class Option {
/**
* @template T
* @param {T} value
*/
static of(value) {
return Some.of(value)
}
static empty() {
return none
}
}
/** /**
* @template T * @template T
* @param {T} value * @param {T} value

View file

@ -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' */ /** @import { Morphism } from './types.js' */
/** /**
* @template T, E * @template T, E
* @type {Ok<T, E> | Err<T, E>} 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} */ /** @type {T} */
#value #value
@ -20,7 +35,7 @@ export class Ok extends Algebra(Setoid, Monad, Foldable) {
} }
/** /**
* @param {Ok<T, E>} other * @param {Result<T, E>} other
*/ */
equals(other) { equals(other) {
return other instanceof Ok && return other instanceof Ok &&
@ -86,7 +101,7 @@ export class Ok extends Algebra(Setoid, Monad, Foldable) {
/** /**
* @template T, E * @template T, E
*/ */
export class Err extends Algebra(Setoid, Functor, Monad) { export class Err extends Result {
/** @type {E} */ /** @type {E} */
#value #value
@ -100,6 +115,9 @@ export class Err extends Algebra(Setoid, Functor, Monad) {
this.#value = value this.#value = value
} }
/**
* @param {Result<T, E>} other
*/
equals(other) { equals(other) {
return other instanceof Err && return other instanceof Err &&
other.chain(v => v === this.#value) other.chain(v => v === this.#value)
@ -158,21 +176,6 @@ export class Err extends Algebra(Setoid, Functor, Monad) {
} }
} }
/**
* @template T, E
* @type {Ok<T, E> | Err<T, E>} Result
*/
export class Result {
/**
* @template T
* @param {T} value
* @returns {Ok}
*/
static of(value) {
return new Ok(value)
}
}
/** /**
* @template T, E * @template T, E
* @param {T} v * @param {T} v

View file

@ -17,17 +17,197 @@ export default {}
/** /**
* @template T * @template T
* @typedef {(value: T) => Chain<T>} ChainConstructor * @typedef {{
equals: (b: Setoid<T>) => boolean
* }} Setoid
*/ */
/** /**
* @template T * @template T
* @typedef {<R>(f: Morphism<T, R>) => R} chain * @typedef {{
lte: (b: Ord<T>) => boolean
* }} Ord
*/
/**
* @template T, U
* @typedef {*} Semigroupoid
*/
/**
* @template I, J
* @typedef {{
compose: <K>(b: Semigroupoid<J, K>) => Semigroupoid<I, K>
* }} Category
*/
/**
* @template T, U
* @template {Category<T, U>} M
* @typedef {{
id: () => (value: T) => M
* }} CategoryTypeRef
*/ */
/** /**
* @template T * @template T
* @typedef {{ chain: chain<T> }} Chain * @typedef {{
concat: (b: Semigroup<T>) => Semigroup<T>
* }} Semigroup
*/
/**
* @template T
* @typedef{Semigroup<T>} Monoid
*/
/**
* @template T
* @template {Monoid<T>} M
* @typedef {{
empty: () => M
* }} MonoidTypeRef
*/
/**
* @template T
* @typedef {
Monoid<T> &
{ invert: () => Group<T> }
* } Group
*/
/**
* @template T
* @typedef {{
filter: <U>(f: (acc: U, val: T) => U, init: U) => U
* }} Filterable
*/
/**
* @template T
* @typedef {{
map: <U>(f: Morphism<T, U>) => Functor<U>
* }} Functor
*/
/**
* @template T
* @typedef {{
contramap: <U>(f: Morphism<U, T>) => Contravariant<T>
* }} Contravariant
*/
/**
* @template T
* @typedef {{
ap: <U>(f: Apply<Morphism<T, U>>) => Apply<U>
* }} Apply
*/
/**
* @template T
* @typedef {{
ap: <U>(b: Applicative<Morphism<U, T>>) => Applicative<T>,
map: <U>(f: Morphism<T, U>) => Applicative<U>
* }} Applicative
*/
/**
* @template T
* @template {Applicative<T>} M
* @typedef {{
of: (value: T) => M
* }} ApplicativeTypeRef
*/
/**
* @template T
* @typedef {
Functor<T> &
{ alt: (b: Alt<T>) => Alt<T> }
* } Alt
*/
/**
* @template T
* @typedef {Alt<T>} Plus
*/
/**
* @template T
* @template {Plus<T>} M
* @typedef {
Alt<T> &
{ zero: () => M }
* } PlusTypeRef
*/
/**
* @template T
* @typedef {Applicative<T> & Plus<T>} Alternative
*/
/**
* @template T
* @template {Applicative<T> & Plus<T>} M
* @typedef {ApplicativeTypeRef<T, M> & PlusTypeRef<T, M>} AlternativeTypeRef
*/
/**
* @template T
* @typedef {{
filter: <U>(f: (acc: U, val: T) => U, init: U) => U
* }} Foldable
*/
/**
* @template T
* @typedef {
Functor<T> & Foldable<T> &
{ traverse: <U>(A: ApplicativeTypeRef<U, Applicative<U>>, f: (val: T) => Applicative<U>) => Applicative<Traversable<U>> }
* } Traversable
*/
/**
* @template T
* @typedef {
Apply<T> &
{ chain: <U>(f: (value: T) => Chain<U>) => Chain<U> }
* } Chain
*/
/**
* @template T
* @typedef {Chain<T>} ChainRec
*/
/**
* @template T
* @typedef {Functor<T> & Applicative<T> & Chain<T>} Monad
*/
/**
* @template T
* @template {Monad<T>} M
* @typedef {ApplicativeTypeRef<T, M>} MonadTypeDef
*/
/**
* @template T
* @typedef {
Functor<T> &
{ extend: <U>(f: (val: Extend<T>) => U) => Extend<U>}
* } Extend
*/
/**
* @template T
* @typedef {
Extend<T> &
{ extract: () => T }
* } Comonad<T>
*/ */
/** @typedef {(...args: any[]) => any} Fn */ /** @typedef {(...args: any[]) => any} Fn */

View file

@ -246,7 +246,7 @@ class MixinBuilder {
/** /**
* Applies `mixins` in order to the superclass given to `mix()`. * Applies `mixins` in order to the superclass given to `mix()`.
* *
* @param {Array.<Mixin>} mixins * @param {Array.<MixinFunction>} mixins
* @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied * @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied
*/ */
with(...mixins) { with(...mixins) {

View file

@ -1,28 +1,29 @@
// @ts-nocheck // @ts-nocheck
import { it, assert } from 'folktest' import { it, assert } from 'folktest'
import { Option, Result } from '../../src/index.js' import { IO, List, Option, Reader, Result, Some } from '../../src/index.js'
import { Chain, Ord, Setoid } from '../../src/algebra/interfaces.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 inc = F => x => F.of(x + 1)
const dbl = x => F.of(x * 2) const dbl = F => x => F.of(x * 2)
const impl = i => m => i.implementedBy(m) const impl = i => m => i.implementedBy(m)
export const Tests = [ export const Tests = [
it('unit is a left identity', () => { it('unit is a left identity', () => {
console.log(Chain.implementedBy(Result)) Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
Algebra.filter(impl(Chain)).forEach(algebra => { const f = inc(algebra)
assert( assert(
algebra.of(1).chain(inc) == inc(1), algebra.of(1).chain(f).equals(f(1)),
`${algebra.name} is not a left identity` `${algebra.name} is not a left identity`
) )
}) })
}), }),
it('unit is a right 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) const m = algebra.of(1)
assert( assert(
@ -33,36 +34,15 @@ export const Tests = [
}), }),
it('unit is associative', () => { it('unit is associative', () => {
Algebra.filter(impl(Chain)).forEach(algebra => { Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => {
const a = algebra.of(1) const a = algebra.of(1)
const f = inc(algebra)
const g = dbl(algebra)
const x = a.chain(inc).chain(dbl) const x = a.chain(f).chain(g)
const y = a.chain(x => inc(x).chain(dbl)) 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))
})
})
] ]