move interfaces to their own file
This commit is contained in:
parent
96cc50ad34
commit
35aa1c6ae9
14 changed files with 389 additions and 457 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
||||||
[submodule "vendor/izuna"]
|
[submodule "vendor/izuna"]
|
||||||
path = vendor/izuna
|
path = vendor/izuna
|
||||||
url = git@git.kitsu.cafe:rowan/izuna.git
|
url = https://git.kitsu.cafe/rowan/izuna.git
|
||||||
|
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -17,14 +17,14 @@
|
||||||
},
|
},
|
||||||
"node_modules/folktest": {
|
"node_modules/folktest": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#b130e6fd1839a32ca62ffe9c96da58d8bdf39b38",
|
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#708d44f1215be33fcceba426029f44b4f963dbe5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GPL-3.0-or-later"
|
"license": "GPL-3.0-or-later"
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.2",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { id, map, ap, prepend, reduce, thunk } from './fn.js'
|
import { id, map, ap, prepend, reduce, thunk } from '../../vendor/izuna/src/index.js'
|
||||||
/** @import { Morphism } from './types.js' */
|
/** @import { Morphism } from './types.js' */
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ export class Pure {
|
||||||
* @returns {B}
|
* @returns {B}
|
||||||
*/
|
*/
|
||||||
chain(f) {
|
chain(f) {
|
||||||
console.log('Pure.chain', this.#value)
|
|
||||||
return f(this.#value)
|
return f(this.#value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ export class Pure {
|
||||||
* @returns {Free<B>}
|
* @returns {Free<B>}
|
||||||
*/
|
*/
|
||||||
map(f) {
|
map(f) {
|
||||||
console.log(`Pure.map ${f} ${this.#value}`)
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return pure(f(this.#value))
|
return pure(f(this.#value))
|
||||||
}
|
}
|
||||||
|
@ -45,7 +43,6 @@ export class Pure {
|
||||||
* @returns {Free<C>}
|
* @returns {Free<C>}
|
||||||
*/
|
*/
|
||||||
ap(b) {
|
ap(b) {
|
||||||
console.log('Pure.ap', b, this.#value)
|
|
||||||
return b.map(this.#value)
|
return b.map(this.#value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +57,6 @@ export class Pure {
|
||||||
* @returns {B}
|
* @returns {B}
|
||||||
*/
|
*/
|
||||||
reduce(f, init) {
|
reduce(f, init) {
|
||||||
console.log('Pure.reduce', init, this.#value)
|
|
||||||
return f(init, this.#value)
|
return f(init, this.#value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,15 +84,10 @@ export class Impure {
|
||||||
* @returns {B}
|
* @returns {B}
|
||||||
*/
|
*/
|
||||||
chain(f) {
|
chain(f) {
|
||||||
console.log('Impure.chain')
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return new Impure(
|
return new Impure(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
() => {
|
() => this.#next().chain(f)
|
||||||
console.log(`Impure.chain<${f}>`, this.#next())
|
|
||||||
this.#next().chain(f)
|
|
||||||
}
|
|
||||||
//() => this.#next().chain(f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,14 +97,9 @@ export class Impure {
|
||||||
* @returns {Free<B>}
|
* @returns {Free<B>}
|
||||||
*/
|
*/
|
||||||
map(f) {
|
map(f) {
|
||||||
console.log(`Impure.map ${f}`)
|
|
||||||
return new Impure(
|
return new Impure(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
() => {
|
() => this.#next().map(f)
|
||||||
console.log(`Impure.map<${f}>`, this.#next())
|
|
||||||
return this.#next().map(f)
|
|
||||||
}
|
|
||||||
//() => this.#next().map(f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +109,6 @@ export class Impure {
|
||||||
* @returns {Free<B>}
|
* @returns {Free<B>}
|
||||||
*/
|
*/
|
||||||
ap(b) {
|
ap(b) {
|
||||||
console.log('Impure.ap', b)
|
|
||||||
return new Impure(
|
return new Impure(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
() => b.map(this.#next())
|
() => b.map(this.#next())
|
||||||
|
@ -148,45 +133,13 @@ export class Impure {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sequence = (of, iter) => {
|
const sequence = (of, iter) => {
|
||||||
console.log(ap(of([]), 1))
|
|
||||||
return reduce((acc, x) => {
|
return reduce((acc, x) => {
|
||||||
ap(acc, map(prepend, x))
|
ap(acc, map(prepend, x))
|
||||||
}, of([]), iter)
|
}, of([]), iter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Reducer {
|
|
||||||
constructor(v) {
|
|
||||||
this.value = v
|
|
||||||
}
|
|
||||||
|
|
||||||
static of(v) {
|
|
||||||
return new Reducer(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
ap(other) {
|
|
||||||
console.log(this, other)
|
|
||||||
return this.value(other.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = Reducer.of
|
|
||||||
console.log(sequence(r, [r(1), r(2), r(3)]))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template A
|
|
||||||
* @param {A} x
|
|
||||||
* @returns {Free<A, A>}
|
|
||||||
*/
|
|
||||||
export const pure = x => new Pure(x)
|
export const pure = x => new Pure(x)
|
||||||
|
|
||||||
/**
|
|
||||||
* @template A, B
|
|
||||||
* @param {Computation<A, B>} f
|
|
||||||
*/
|
|
||||||
export const impure = f => new Impure(f)
|
export const impure = f => new Impure(f)
|
||||||
|
|
||||||
export const liftF = effect => impure(thunk(effect))
|
export const liftF = effect => impure(thunk(effect))
|
||||||
|
|
||||||
const xs = liftF(pure(1)).map(thunk(2)).map(thunk(3))
|
|
||||||
console.log(xs.traverse(id, id))
|
|
||||||
|
|
||||||
|
|
|
@ -1,361 +1,7 @@
|
||||||
import { mix } from '../mixin.js'
|
export * from './option.js'
|
||||||
|
export * from './result.js'
|
||||||
/** @import { MixinFunction } from '../mixin.js' */
|
export * from './list.js'
|
||||||
/** @import { Fn } from './types.js' */
|
export * from './free.js'
|
||||||
|
export * from './io.js'
|
||||||
export const ProtectedConstructor = Symbol('ProtectedConstructor')
|
export * from './reader.js'
|
||||||
export class ProtectedConstructorError extends Error {
|
|
||||||
/** @param {string} name */
|
|
||||||
constructor(name) {
|
|
||||||
super(`ProtectedConstructorError: ${name} cannot be directly constructed`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Derivations {
|
|
||||||
#inner
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Iterable<readonly [any, any]>} iterable
|
|
||||||
*/
|
|
||||||
constructor(iterable) {
|
|
||||||
this.#inner = new Map(iterable)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Iterable<any>} keys
|
|
||||||
* @param {any} value
|
|
||||||
* @returns {this}
|
|
||||||
*/
|
|
||||||
set(keys, value) {
|
|
||||||
this.#inner.set(new Set(keys), value)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Iterable<any>} keys
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
has(keys) {
|
|
||||||
return this.#inner.has(new Set(keys))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Iterable<any>} keys
|
|
||||||
*/
|
|
||||||
get(keys) {
|
|
||||||
return this.#inner.get(new Set(keys))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Iterable<any>} keys
|
|
||||||
*/
|
|
||||||
delete(keys) {
|
|
||||||
return this.#inner.delete(new Set(keys))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotImplementedError extends Error {
|
|
||||||
/** @param {string} name */
|
|
||||||
constructor(name) {
|
|
||||||
super(`${name} is not implemented`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @typedef {(mixin: MixinFunction) => MixinFunction} MixinWrapper */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @enum {number}
|
|
||||||
*/
|
|
||||||
const MethodType = Object.freeze({
|
|
||||||
Instance: 0,
|
|
||||||
Static: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
class Method {
|
|
||||||
#name
|
|
||||||
|
|
||||||
/** @type {MethodType} */
|
|
||||||
#type = MethodType.Instance
|
|
||||||
|
|
||||||
/** @type {Fn} */
|
|
||||||
#implementation
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | Method} name
|
|
||||||
*/
|
|
||||||
constructor(name) {
|
|
||||||
if (name instanceof Method) {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | Method} value
|
|
||||||
*/
|
|
||||||
static from(value) {
|
|
||||||
return new Method(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
isInstance() {
|
|
||||||
this.#type = MethodType.Instance
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
isStatic() {
|
|
||||||
this.#type = MethodType.Static
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {Fn} f */
|
|
||||||
implementation(f) {
|
|
||||||
this.#implementation = f
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} interfaceName
|
|
||||||
*/
|
|
||||||
_defaultImplementation(interfaceName) {
|
|
||||||
const err = new NotImplementedError(
|
|
||||||
`${interfaceName}::${this.#name}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return function() { throw err }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} target
|
|
||||||
*/
|
|
||||||
_getInstallationPoint(target) {
|
|
||||||
switch (this.#type) {
|
|
||||||
case 0: return target.prototype
|
|
||||||
case 1: return target
|
|
||||||
default: return target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Interface} builder
|
|
||||||
* @param {Function} target
|
|
||||||
*/
|
|
||||||
implement(builder, target) {
|
|
||||||
|
|
||||||
const impl = this.#implementation || this._defaultImplementation(builder.name)
|
|
||||||
|
|
||||||
this._getInstallationPoint(target)[this.#name] = impl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Interface {
|
|
||||||
#name
|
|
||||||
|
|
||||||
/** @type {Set<Method>} */
|
|
||||||
#methods = new Set()
|
|
||||||
|
|
||||||
/** @type {Set<Interface>} */
|
|
||||||
#interfaces = new Set()
|
|
||||||
|
|
||||||
/** @param {string} name */
|
|
||||||
constructor(name) {
|
|
||||||
this.#name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this.#name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {...(PropertyKey | Method)} methods
|
|
||||||
* @returns {this}
|
|
||||||
*/
|
|
||||||
specifies(...methods) {
|
|
||||||
this.#methods = new Set(
|
|
||||||
methods
|
|
||||||
.map(Method.from)
|
|
||||||
.concat(...this.#methods)
|
|
||||||
)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {...Interface} interfaces
|
|
||||||
* @returns {this}
|
|
||||||
*/
|
|
||||||
extends(...interfaces) {
|
|
||||||
this.#interfaces = new Set(interfaces.concat(...this.#interfaces))
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {MixinWrapper}
|
|
||||||
*/
|
|
||||||
intoWrapper() {
|
|
||||||
return this.build.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {FunctionConstructor} base
|
|
||||||
*/
|
|
||||||
build(base) {
|
|
||||||
const interfaces = [...this.#interfaces.values()].map(x => x.intoWrapper())
|
|
||||||
|
|
||||||
const Interfaces = mix(base).with(...interfaces)
|
|
||||||
|
|
||||||
const Algebra = class extends Interfaces { }
|
|
||||||
|
|
||||||
for (const method of this.#methods) {
|
|
||||||
method.implement(this, Algebra)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Algebra
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @template T */
|
|
||||||
export class BaseSet {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {PropertyKey} key
|
|
||||||
* @param {object} obj
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export const hasOwn = (key, obj) => Object.hasOwn(obj, key)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} left
|
|
||||||
* @param {Function} right
|
|
||||||
*/
|
|
||||||
export const EitherOf = (left, right) => mix(left).with(l => right(l))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} methodName
|
|
||||||
* @param {(...args: any[]) => any} f
|
|
||||||
* @param {object} obj
|
|
||||||
*/
|
|
||||||
export const apply = (methodName, f, obj) => {
|
|
||||||
if (hasOwn(methodName, obj)) {
|
|
||||||
return obj[methodName](f)
|
|
||||||
} else {
|
|
||||||
return 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 = AlgebraWithBase(BaseSet)
|
|
||||||
|
|
||||||
export const Setoid = new Interface('Setoid')
|
|
||||||
.specifies('equals')
|
|
||||||
|
|
||||||
export const Ord = new Interface('Ord')
|
|
||||||
.specifies('lte')
|
|
||||||
|
|
||||||
export const Semigroupoid = new Interface('Semigroupoid')
|
|
||||||
.specifies('compose')
|
|
||||||
|
|
||||||
export const Category = new Interface('Category')
|
|
||||||
.extends(Semigroupoid)
|
|
||||||
.specifies(
|
|
||||||
Method.from('id').isStatic()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Semigroup = new Interface('Semigroup')
|
|
||||||
.specifies('concat')
|
|
||||||
|
|
||||||
export const Monoid = new Interface('Monoid')
|
|
||||||
.extends(Semigroup)
|
|
||||||
.specifies(
|
|
||||||
Method.from('empty').isStatic()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Group = new Interface('Group')
|
|
||||||
.extends(Monoid)
|
|
||||||
.specifies('invert')
|
|
||||||
|
|
||||||
export const Filterable = new Interface('Filterable')
|
|
||||||
.specifies('filter')
|
|
||||||
|
|
||||||
export const Functor = new Interface('Functor')
|
|
||||||
.specifies('map')
|
|
||||||
|
|
||||||
export const Contravariant = new Interface('Contravariant')
|
|
||||||
.specifies('contramap')
|
|
||||||
|
|
||||||
export const Apply = new Interface('Apply')
|
|
||||||
.extends(Functor)
|
|
||||||
.specifies('ap')
|
|
||||||
|
|
||||||
export const Applicative = new Interface('Applicative')
|
|
||||||
.extends(Apply)
|
|
||||||
.specifies(
|
|
||||||
Method.from('of').isStatic()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Alt = new Interface('Alt')
|
|
||||||
.extends(Functor)
|
|
||||||
.specifies('alt')
|
|
||||||
|
|
||||||
export const Plus = new Interface('Plus')
|
|
||||||
.extends(Alt)
|
|
||||||
.specifies(
|
|
||||||
Method.from('zero').isStatic()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Alternative = new Interface('Alternative')
|
|
||||||
.extends(Applicative, Plus)
|
|
||||||
|
|
||||||
export const Foldable = new Interface('Foldable')
|
|
||||||
.specifies('fold')
|
|
||||||
|
|
||||||
export const Traversable = new Interface('Traversable')
|
|
||||||
.extends(Functor, Foldable)
|
|
||||||
.specifies('traverse')
|
|
||||||
|
|
||||||
export const Chain = new Interface('Chain')
|
|
||||||
.extends(Apply)
|
|
||||||
.specifies(
|
|
||||||
Method.from('chain')
|
|
||||||
.implementation(function(f) {
|
|
||||||
return f(this._value)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const ChainRef = new Interface('ChainRec')
|
|
||||||
.extends(Chain)
|
|
||||||
.specifies(
|
|
||||||
Method.from('chainRec').isStatic()
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Monad = new Interface('Monad')
|
|
||||||
.extends(Applicative, Chain)
|
|
||||||
|
|
||||||
export const Extend = new Interface('Extend')
|
|
||||||
.extends(Functor)
|
|
||||||
.specifies('extend')
|
|
||||||
|
|
||||||
export const Comonad = new Interface('Comonad')
|
|
||||||
.extends(Extend)
|
|
||||||
.specifies('extract')
|
|
||||||
|
|
||||||
export const Bifunctor = new Interface('Bifunctor')
|
|
||||||
.extends(Functor)
|
|
||||||
.specifies('bimap')
|
|
||||||
|
|
||||||
export const Profunctor = new Interface('Profunctor')
|
|
||||||
.extends(Functor)
|
|
||||||
.specifies('promap')
|
|
||||||
|
|
||||||
|
|
361
src/algebra/interfaces.js
Normal file
361
src/algebra/interfaces.js
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
import { mix } from '../mixin.js'
|
||||||
|
|
||||||
|
/** @import { MixinFunction } from '../mixin.js' */
|
||||||
|
/** @import { Fn } from './types.js' */
|
||||||
|
|
||||||
|
export const ProtectedConstructor = Symbol('ProtectedConstructor')
|
||||||
|
export class ProtectedConstructorError extends Error {
|
||||||
|
/** @param {string} name */
|
||||||
|
constructor(name) {
|
||||||
|
super(`ProtectedConstructorError: ${name} cannot be directly constructed`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Derivations {
|
||||||
|
#inner
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Iterable<readonly [any, any]>} iterable
|
||||||
|
*/
|
||||||
|
constructor(iterable) {
|
||||||
|
this.#inner = new Map(iterable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Iterable<any>} keys
|
||||||
|
* @param {any} value
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
set(keys, value) {
|
||||||
|
this.#inner.set(new Set(keys), value)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Iterable<any>} keys
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has(keys) {
|
||||||
|
return this.#inner.has(new Set(keys))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Iterable<any>} keys
|
||||||
|
*/
|
||||||
|
get(keys) {
|
||||||
|
return this.#inner.get(new Set(keys))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Iterable<any>} keys
|
||||||
|
*/
|
||||||
|
delete(keys) {
|
||||||
|
return this.#inner.delete(new Set(keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotImplementedError extends Error {
|
||||||
|
/** @param {string} name */
|
||||||
|
constructor(name) {
|
||||||
|
super(`${name} is not implemented`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @typedef {(mixin: MixinFunction) => MixinFunction} MixinWrapper */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
const MethodType = Object.freeze({
|
||||||
|
Instance: 0,
|
||||||
|
Static: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
class Method {
|
||||||
|
#name
|
||||||
|
|
||||||
|
/** @type {MethodType} */
|
||||||
|
#type = MethodType.Instance
|
||||||
|
|
||||||
|
/** @type {Fn} */
|
||||||
|
#implementation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | Method} name
|
||||||
|
*/
|
||||||
|
constructor(name) {
|
||||||
|
if (name instanceof Method) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | Method} value
|
||||||
|
*/
|
||||||
|
static from(value) {
|
||||||
|
return new Method(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
isInstance() {
|
||||||
|
this.#type = MethodType.Instance
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
this.#type = MethodType.Static
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Fn} f */
|
||||||
|
implementation(f) {
|
||||||
|
this.#implementation = f
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} interfaceName
|
||||||
|
*/
|
||||||
|
_defaultImplementation(interfaceName) {
|
||||||
|
const err = new NotImplementedError(
|
||||||
|
`${interfaceName}::${this.#name}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return function() { throw err }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Function} target
|
||||||
|
*/
|
||||||
|
_getInstallationPoint(target) {
|
||||||
|
switch (this.#type) {
|
||||||
|
case 0: return target.prototype
|
||||||
|
case 1: return target
|
||||||
|
default: return target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Interface} builder
|
||||||
|
* @param {Function} target
|
||||||
|
*/
|
||||||
|
implement(builder, target) {
|
||||||
|
|
||||||
|
const impl = this.#implementation || this._defaultImplementation(builder.name)
|
||||||
|
|
||||||
|
this._getInstallationPoint(target)[this.#name] = impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Interface {
|
||||||
|
#name
|
||||||
|
|
||||||
|
/** @type {Set<Method>} */
|
||||||
|
#methods = new Set()
|
||||||
|
|
||||||
|
/** @type {Set<Interface>} */
|
||||||
|
#interfaces = new Set()
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
|
constructor(name) {
|
||||||
|
this.#name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.#name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {...(PropertyKey | Method)} methods
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
specifies(...methods) {
|
||||||
|
this.#methods = new Set(
|
||||||
|
methods
|
||||||
|
.map(Method.from)
|
||||||
|
.concat(...this.#methods)
|
||||||
|
)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {...Interface} interfaces
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
extends(...interfaces) {
|
||||||
|
this.#interfaces = new Set(interfaces.concat(...this.#interfaces))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {MixinWrapper}
|
||||||
|
*/
|
||||||
|
intoWrapper() {
|
||||||
|
return this.build.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FunctionConstructor} base
|
||||||
|
*/
|
||||||
|
build(base) {
|
||||||
|
const interfaces = [...this.#interfaces.values()].map(x => x.intoWrapper())
|
||||||
|
|
||||||
|
const Interfaces = mix(base).with(...interfaces)
|
||||||
|
|
||||||
|
const Algebra = class extends Interfaces { }
|
||||||
|
|
||||||
|
for (const method of this.#methods) {
|
||||||
|
method.implement(this, Algebra)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Algebra
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
export class BaseSet {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PropertyKey} key
|
||||||
|
* @param {object} obj
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const hasOwn = (key, obj) => Object.hasOwn(obj, key)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Function} left
|
||||||
|
* @param {Function} right
|
||||||
|
*/
|
||||||
|
export const EitherOf = (left, right) => mix(left).with(l => right(l))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} methodName
|
||||||
|
* @param {(...args: any[]) => any} f
|
||||||
|
* @param {object} obj
|
||||||
|
*/
|
||||||
|
export const apply = (methodName, f, obj) => {
|
||||||
|
if (hasOwn(methodName, obj)) {
|
||||||
|
return obj[methodName](f)
|
||||||
|
} else {
|
||||||
|
return 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 = AlgebraWithBase(BaseSet)
|
||||||
|
|
||||||
|
export const Setoid = new Interface('Setoid')
|
||||||
|
.specifies('equals')
|
||||||
|
|
||||||
|
export const Ord = new Interface('Ord')
|
||||||
|
.specifies('lte')
|
||||||
|
|
||||||
|
export const Semigroupoid = new Interface('Semigroupoid')
|
||||||
|
.specifies('compose')
|
||||||
|
|
||||||
|
export const Category = new Interface('Category')
|
||||||
|
.extends(Semigroupoid)
|
||||||
|
.specifies(
|
||||||
|
Method.from('id').isStatic()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Semigroup = new Interface('Semigroup')
|
||||||
|
.specifies('concat')
|
||||||
|
|
||||||
|
export const Monoid = new Interface('Monoid')
|
||||||
|
.extends(Semigroup)
|
||||||
|
.specifies(
|
||||||
|
Method.from('empty').isStatic()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Group = new Interface('Group')
|
||||||
|
.extends(Monoid)
|
||||||
|
.specifies('invert')
|
||||||
|
|
||||||
|
export const Filterable = new Interface('Filterable')
|
||||||
|
.specifies('filter')
|
||||||
|
|
||||||
|
export const Functor = new Interface('Functor')
|
||||||
|
.specifies('map')
|
||||||
|
|
||||||
|
export const Contravariant = new Interface('Contravariant')
|
||||||
|
.specifies('contramap')
|
||||||
|
|
||||||
|
export const Apply = new Interface('Apply')
|
||||||
|
.extends(Functor)
|
||||||
|
.specifies('ap')
|
||||||
|
|
||||||
|
export const Applicative = new Interface('Applicative')
|
||||||
|
.extends(Apply)
|
||||||
|
.specifies(
|
||||||
|
Method.from('of').isStatic()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Alt = new Interface('Alt')
|
||||||
|
.extends(Functor)
|
||||||
|
.specifies('alt')
|
||||||
|
|
||||||
|
export const Plus = new Interface('Plus')
|
||||||
|
.extends(Alt)
|
||||||
|
.specifies(
|
||||||
|
Method.from('zero').isStatic()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Alternative = new Interface('Alternative')
|
||||||
|
.extends(Applicative, Plus)
|
||||||
|
|
||||||
|
export const Foldable = new Interface('Foldable')
|
||||||
|
.specifies('fold')
|
||||||
|
|
||||||
|
export const Traversable = new Interface('Traversable')
|
||||||
|
.extends(Functor, Foldable)
|
||||||
|
.specifies('traverse')
|
||||||
|
|
||||||
|
export const Chain = new Interface('Chain')
|
||||||
|
.extends(Apply)
|
||||||
|
.specifies(
|
||||||
|
Method.from('chain')
|
||||||
|
.implementation(function(f) {
|
||||||
|
return f(this._value)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ChainRef = new Interface('ChainRec')
|
||||||
|
.extends(Chain)
|
||||||
|
.specifies(
|
||||||
|
Method.from('chainRec').isStatic()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Monad = new Interface('Monad')
|
||||||
|
.extends(Applicative, Chain)
|
||||||
|
|
||||||
|
export const Extend = new Interface('Extend')
|
||||||
|
.extends(Functor)
|
||||||
|
.specifies('extend')
|
||||||
|
|
||||||
|
export const Comonad = new Interface('Comonad')
|
||||||
|
.extends(Extend)
|
||||||
|
.specifies('extract')
|
||||||
|
|
||||||
|
export const Bifunctor = new Interface('Bifunctor')
|
||||||
|
.extends(Functor)
|
||||||
|
.specifies('bimap')
|
||||||
|
|
||||||
|
export const Profunctor = new Interface('Profunctor')
|
||||||
|
.extends(Functor)
|
||||||
|
.specifies('promap')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Algebra, Monad } from './index.js'
|
import { Algebra, Monad } from './interfaces.js'
|
||||||
|
|
||||||
/** @import { Fn, InferredMorphism, Morphism } from './types.js' */
|
/** @import { Fn, InferredMorphism, Morphism } from './types.js' */
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Pure, Suspend } from './free.js'
|
import { Algebra, Comonad, Foldable, Monoid, Semigroup } from './interfaces.js'
|
||||||
import { Algebra, AlgebraWithBase, Comonad, Foldable, Monoid, Semigroup } from './index.js'
|
|
||||||
|
|
||||||
/** @import { InferredMorphism, Morphism } from './types.js' */
|
/** @import { InferredMorphism, Morphism } from './types.js' */
|
||||||
|
|
||||||
|
@ -188,15 +187,5 @@ export class List {
|
||||||
|
|
||||||
const empty = new Empty()
|
const empty = new Empty()
|
||||||
|
|
||||||
/**
|
export const list = value => List.of(value)
|
||||||
* @template T
|
|
||||||
* @param {T[]} acc
|
|
||||||
* @param {T} value
|
|
||||||
* @returns {T[]}
|
|
||||||
*/
|
|
||||||
const reduceArray = (acc, value) => acc.concat(value)
|
|
||||||
|
|
||||||
const arr = Array.from({ length: 10 })
|
|
||||||
const list = List.from(arr)
|
|
||||||
list.map(x => x * 2)
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Algebra, Foldable, Monad, Monoid } from './index.js'
|
import { Algebra, Foldable, Monad, Monoid } from './interfaces.js'
|
||||||
|
|
||||||
/** @import { Morphism } from './types.js' */
|
/** @import { Morphism } from './types.js' */
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export class Some extends Algebra(Monoid, Monad, Foldable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @template T */
|
/** @template T */
|
||||||
class None extends Algebra(Monoid, Monad, Foldable) {
|
export class None extends Algebra(Monoid, Monad, Foldable) {
|
||||||
/**
|
/**
|
||||||
* @template R
|
* @template R
|
||||||
* @param {Morphism<T, R>} _f
|
* @param {Morphism<T, R>} _f
|
||||||
|
@ -132,6 +132,5 @@ export class Option {
|
||||||
* @param {T} value
|
* @param {T} value
|
||||||
*/
|
*/
|
||||||
export const some = value => new Some(value)
|
export const some = value => new Some(value)
|
||||||
|
|
||||||
export const none = new None()
|
export const none = new None()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { id } from './fn.js'
|
import { id } from '../../vendor/izuna/src/index.js'
|
||||||
import { Algebra, Monad } from './index.js'
|
import { Algebra, Monad } from './interfaces.js'
|
||||||
|
|
||||||
/** @import { InferredMorphism, Morphism } from './types.js' */
|
/** @import { InferredMorphism, Morphism } from './types.js' */
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Algebra, Foldable, Functor, Monad } from './index.js'
|
import { Algebra, Foldable, Functor, Monad } from './interfaces.js'
|
||||||
|
|
||||||
/** @import { Morphism } from './types.js' */
|
/** @import { Morphism } from './types.js' */
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './algebra/index.js'
|
export * from './algebra/index.js'
|
||||||
export * from './curry.js'
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { TerminalRunner } from 'folktest'
|
||||||
import * as Tests from './units/index.js'
|
import * as Tests from './units/index.js'
|
||||||
|
|
||||||
const ap = f => f()
|
console.log(TerminalRunner(Tests).toString())
|
||||||
const fmt = ({ success, description, error }) => {
|
|
||||||
if (success) {
|
|
||||||
return `${description}: PASS`
|
|
||||||
} else {
|
|
||||||
return `${description}: FAIL\n${error.stack}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = Object.entries(Tests).map(([name, tests]) => ({
|
|
||||||
name,
|
|
||||||
tests: tests.map(ap).map(fmt).join('\n')
|
|
||||||
}))
|
|
||||||
.map(({ name, tests }) => `${name}\n${tests}`)
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
console.log(results)
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { it, assertEq } from 'folktest'
|
import { it, assertEq } from 'folktest'
|
||||||
import { Identity, Constant, Ok, Err, Some, None } from '../../src/index.js'
|
import { Ok, Err, Some, None } from '../../src/index.js'
|
||||||
|
|
||||||
const leftIdentity = (M, a, f) => {
|
const leftIdentity = (M, a, f) => {
|
||||||
assertEq(M(a).bind(f), f(a))
|
assertEq(M(a).bind(f), f(a))
|
||||||
|
@ -21,7 +21,7 @@ export const prove = (M, m, a, f, g) => {
|
||||||
|
|
||||||
export const Tests = [
|
export const Tests = [
|
||||||
it('should adhere to monadic laws', () => {
|
it('should adhere to monadic laws', () => {
|
||||||
[Identity, Constant, Ok, Err, Some, None].forEach(ctr => {
|
[Ok, Err, Some, None].forEach(ctr => {
|
||||||
prove(
|
prove(
|
||||||
ctr,
|
ctr,
|
||||||
ctr(1),
|
ctr(1),
|
||||||
|
|
2
vendor/izuna
vendored
2
vendor/izuna
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit d9daed0d0977f7b79462fb204a5d89b827dcac1b
|
Subproject commit aa70427c8c349bbfe4576cba878f5b44859007d4
|
Loading…
Add table
Reference in a new issue