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 { 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<A> | Impure<A>} Free
* @template T
* @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 */
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>} B
* @param {Morphism<A, B>} f
* @returns {B}
* @param {InferredMorphism<T>} f
*/
chain(f) {
return f(this.#value)
}
/**
* @template B
* @param {Morphism<A, B>} f
* @returns {Free<B>}
* @param {InferredMorphism<T>} f
*/
map(f) {
// @ts-ignore
return pure(f(this.#value))
}
/**
* @template B, C
* @param {Free<B>} b
* @returns {Free<C>}
* @template U
* @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} 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 {<B>(value: A) => Free<B>} Computation
*/
/** @template A */
export class Impure {
/** @template T */
export class Impure extends Free {
#value
#next
/**
* @param {() => Free<any>} next
* @param {T} value
* @param {(value: T) => Free<any>} f
*/
constructor(next) {
this.#next = next
constructor(value, f) {
super()
this.#value = value
this.#next = f
}
/**
* @template {Free<B>} B
* @param {Morphism<A, B>} f
* @returns {B}
* @param {<U>(value: T) => Free<U>} 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<A, B>} f
* @returns {Free<B>}
*/
map(f) {
return new Impure(
// @ts-ignore
() => this.#next().map(f)
return impure(
this.#value,
y => f(y).chain(f)
)
}
/**
* @template B
* @param {Free<any>} b
* @returns {Free<B>}
* @template U
* @template {Applicative<U>} M
* @template {ApplicativeTypeRef<U, M>} 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<T>}
*/
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<any>} f
* @returns {Impure<T>}
*/
const impure = (x, f) => new Impure(x, f)
export const liftF = effect => impure(thunk(effect))
/**
* @template T
* @param {T} x
* @returns Impure<T>
*/
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())

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 { 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<Method>} */
#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()))
}
/**

View file

@ -2,8 +2,22 @@ import { Algebra, Foldable, Monad, Monoid, Setoid } from './interfaces.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 */
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<T>} 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<T, R>} 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<T>} other
*/
equals(other) {
return other === none
}
ap(_b) {
return this
}
/**
* @template R
* @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
* @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' */
/**
* @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} */
#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) {
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<T, E>} 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<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
* @param {T} v

View file

@ -17,17 +17,197 @@ export default {}
/**
* @template T
* @typedef {(value: T) => Chain<T>} ChainConstructor
* @typedef {{
equals: (b: Setoid<T>) => boolean
* }} Setoid
*/
/**
* @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
* @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 */

View file

@ -246,7 +246,7 @@ class MixinBuilder {
/**
* 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
*/
with(...mixins) {

View file

@ -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))
})
})
]