361 lines
6.8 KiB
JavaScript
361 lines
6.8 KiB
JavaScript
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*/
|
|
class BaseSet {
|
|
_value
|
|
|
|
/** @param {T} value */
|
|
constructor(value) {
|
|
this._value = value
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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 {...Interface} algebras
|
|
* @returns {FunctionConstructor}
|
|
*/
|
|
export const Algebra = (...algebras) => {
|
|
return mix(BaseSet).with(...algebras.map(x => x.intoWrapper()))
|
|
}
|
|
|
|
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')
|
|
|