:3c
This commit is contained in:
parent
d2051d9086
commit
699a8e6a7c
13 changed files with 1138 additions and 284 deletions
|
@ -2,7 +2,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "es2020",
|
"module": "es2020",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": ["es2019", "dom"],
|
"lib": ["es2022", "dom"],
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"/*": ["./*"]
|
"/*": ["./*"]
|
||||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -8,6 +8,9 @@
|
||||||
"name": "kojima",
|
"name": "kojima",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"typescript": "^5.8.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
||||||
}
|
}
|
||||||
|
@ -17,6 +20,19 @@
|
||||||
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#b130e6fd1839a32ca62ffe9c96da58d8bdf39b38",
|
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#b130e6fd1839a32ca62ffe9c96da58d8bdf39b38",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GPL-3.0-or-later"
|
"license": "GPL-3.0-or-later"
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||||
|
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -8,9 +8,17 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./tests/index.js"
|
"test": "./tests/index.js"
|
||||||
},
|
},
|
||||||
"keywords": ["functional", "functional programming", "fp", "monad"],
|
"keywords": [
|
||||||
|
"functional",
|
||||||
|
"functional programming",
|
||||||
|
"fp",
|
||||||
|
"monad"
|
||||||
|
],
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"typescript": "^5.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
export const Self = Symbol()
|
|
||||||
export const Value = Symbol()
|
|
||||||
|
|
||||||
export function constant() { return this }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @typedef {(x: T) => U} Morphism
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {{ [Value]: T }} Set
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @this {Construct<T>}
|
|
||||||
* @typedef {{ [Self]: <a>(value: a) => Construct<a> }} Construct
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {T} value
|
|
||||||
* @returns {Set<T>}
|
|
||||||
*/
|
|
||||||
export const Set = value => ({ [Value]: value })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @param {Morphism<T, U>} fn
|
|
||||||
* @returns {U}
|
|
||||||
*/
|
|
||||||
export function chain(fn) {
|
|
||||||
return fn(this[Value])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @this {Set<T>}
|
|
||||||
* @returns {T}
|
|
||||||
*/
|
|
||||||
export function extract() {
|
|
||||||
return this[Value]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @this {Construct<T>}
|
|
||||||
* @param {U} value
|
|
||||||
* @returns {Construct<U>}
|
|
||||||
*/
|
|
||||||
export function of(value) {
|
|
||||||
return this[Self](value)
|
|
||||||
}
|
|
||||||
|
|
45
src/algebra/fn.js
Normal file
45
src/algebra/fn.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { curry } from '../curry.js'
|
||||||
|
import { Free, Suspend, pure } from './free.js'
|
||||||
|
|
||||||
|
/** @import {InferredMorphism, Morphism, ChainConstructor} from './types.js' */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} x
|
||||||
|
* @returns {x}
|
||||||
|
*/
|
||||||
|
export const id = x => x
|
||||||
|
|
||||||
|
export const compose = curry(
|
||||||
|
/**
|
||||||
|
* @template T, U, V
|
||||||
|
* @param {Morphism<U, V>} f
|
||||||
|
* @param {Morphism<T, U>} g
|
||||||
|
* @param {T} x
|
||||||
|
* @returns V
|
||||||
|
*/
|
||||||
|
(f, g, x) => f(g(x))
|
||||||
|
)
|
||||||
|
|
||||||
|
export const kleisliCompose = curry(
|
||||||
|
/**
|
||||||
|
* @template {ChainConstructor<T>} M, T, U
|
||||||
|
* @param {M} f
|
||||||
|
* @param {Morphism<T, U>} g
|
||||||
|
* @param {T} x
|
||||||
|
* @returns U
|
||||||
|
*/
|
||||||
|
(f, g, x) => f(x).chain(g)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} x
|
||||||
|
* @returns {Free<T>}
|
||||||
|
*/
|
||||||
|
export const liftF = x => new Suspend(
|
||||||
|
x,
|
||||||
|
/** @type {InferredMorphism<T>} */(pure)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
140
src/algebra/free.js
Normal file
140
src/algebra/free.js
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import { liftF } from './fn.js'
|
||||||
|
import { Algebra, Comonad, EitherOf, Functor, Monad } from './index.js'
|
||||||
|
|
||||||
|
/** @import { InferredMorphism, Morphism } from './types.js' */
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
export class Suspend extends Algebra(Functor, Monad) {
|
||||||
|
#value
|
||||||
|
#fn
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {T} value
|
||||||
|
* @param {InferredMorphism<T>} fn
|
||||||
|
*/
|
||||||
|
constructor(value, fn) {
|
||||||
|
super()
|
||||||
|
this.#value = value
|
||||||
|
this.#fn = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {this is Pure<T>} */
|
||||||
|
isPure() { return false }
|
||||||
|
|
||||||
|
/** @returns {this is Suspend<T>} */
|
||||||
|
isSuspend() { return true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {Morphism<T, Free<U>>} f
|
||||||
|
* @returns {Suspend<T>}
|
||||||
|
*/
|
||||||
|
chain(f) {
|
||||||
|
return new Suspend(this.#value, x => this.#fn(x).chain(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {Morphism<T, U>} g
|
||||||
|
* @returns {Suspend<T>}
|
||||||
|
*/
|
||||||
|
map(g) {
|
||||||
|
return new Suspend(this.#value, x => this.#fn(x).map(g))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {Morphism<T, U>} g
|
||||||
|
* @returns {Suspend<T>}
|
||||||
|
*/
|
||||||
|
then(g) {
|
||||||
|
return this.map(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
return this.#fn(this.#value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
export class Pure extends Algebra(Functor, Monad, Comonad) {
|
||||||
|
#value
|
||||||
|
|
||||||
|
/** @param {T} value */
|
||||||
|
constructor(value) {
|
||||||
|
super()
|
||||||
|
this.#value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {this is Pure<T>} */
|
||||||
|
isPure() { return true }
|
||||||
|
|
||||||
|
/** @returns {this is Suspend<T>} */
|
||||||
|
isSuspend() { return false }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {Morphism<T, Pure<U>>} f
|
||||||
|
* @returns {Pure<U>}
|
||||||
|
*/
|
||||||
|
chain(f) {
|
||||||
|
return f(this.#value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {Morphism<T, U>} f
|
||||||
|
* @returns {Pure<U>}
|
||||||
|
*/
|
||||||
|
map(f) {
|
||||||
|
return this.chain(x => new Pure(f(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @returns {Pure<R>}
|
||||||
|
*/
|
||||||
|
then(f) {
|
||||||
|
return this.map(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
extract() {
|
||||||
|
return this.#value
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
return this.#value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @type {Pure<T> | Suspend<T>} Free
|
||||||
|
*/
|
||||||
|
export class Free {
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @returns {Free<T>}
|
||||||
|
*/
|
||||||
|
static of(value) {
|
||||||
|
return liftF(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @returns {Pure<T>}
|
||||||
|
*/
|
||||||
|
export const pure = value => new Pure(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @param {InferredMorphism<T>} f
|
||||||
|
* @returns {Suspend<T>}
|
||||||
|
*/
|
||||||
|
export const suspend = (value, f) => new Suspend(value, f)
|
||||||
|
|
|
@ -1,3 +1,330 @@
|
||||||
export { Some, None, Option } from './option.js'
|
import { mix } from '../mixin.js'
|
||||||
export { Ok, Err, Result } from './result.js'
|
|
||||||
|
/** @import { MixinFunction } from '../mixin.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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {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 err = new NotImplementedError(
|
||||||
|
`${builder.name}::${this.#name}`
|
||||||
|
)
|
||||||
|
|
||||||
|
this._getInstallationPoint(target)[this.#name] = function() {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {...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('chain')
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
54
src/algebra/list.js
Normal file
54
src/algebra/list.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { liftF } from './fn.js'
|
||||||
|
import { Free } from './free.js'
|
||||||
|
import { Algebra, Foldable, Functor, Monad } from './index.js'
|
||||||
|
import { none as None } from './option.js'
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
export class List extends Algebra(Functor, Monad, Foldable) {
|
||||||
|
/** @type {Free<T> | None<T>} */
|
||||||
|
_head
|
||||||
|
|
||||||
|
/** @type {List<T>} */
|
||||||
|
_tail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {T | None} [head]
|
||||||
|
* @param {List<T>} [tail=List]
|
||||||
|
*/
|
||||||
|
constructor(head = None, tail = List.of(None)) {
|
||||||
|
super()
|
||||||
|
this._head = head
|
||||||
|
this._tail = tail
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @returns {List<T>}
|
||||||
|
*/
|
||||||
|
prepend(value) {
|
||||||
|
return new List(value, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
* @returns {List<T>}
|
||||||
|
*/
|
||||||
|
static of(value) {
|
||||||
|
return new List(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {Iterable<T>} iterable
|
||||||
|
* @returns {List<T>}
|
||||||
|
*/
|
||||||
|
static from(iterable) {
|
||||||
|
Array.from(iterable).reduceRight((list, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = liftF(1)
|
||||||
|
console.log(list.map(_ => 2).map(_ => 3).chain(x => x).run())
|
||||||
|
|
|
@ -1,122 +1,125 @@
|
||||||
import { chain, constant, Self, Value } from './common.js'
|
import { Algebra, Foldable, Functor, Monad } from './index.js'
|
||||||
|
|
||||||
/** @import { Construct, Morphism, Set } from './common.js' */
|
/** @import { Morphism } from './types.js' */
|
||||||
|
|
||||||
/**
|
/** @template T */
|
||||||
|
export class Some extends Algebra(Monad, Foldable) {
|
||||||
|
/** @type {T} */
|
||||||
|
#value
|
||||||
|
|
||||||
|
/** @param {T} value */
|
||||||
|
constructor(value) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.#value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
* @typedef {Some<T> | None} Option
|
* @param {T} value
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef SomeMethods
|
|
||||||
* @property {typeof isSome} isSome
|
|
||||||
* @property {typeof isNone} isNone
|
|
||||||
* @property {typeof chain} chain
|
|
||||||
* @property {typeof map} map
|
|
||||||
* @property {typeof alt} alt
|
|
||||||
* @property {typeof fold} fold
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {Construct<T> & Set<T> & SomeMethods<T>} Some
|
|
||||||
* @variation 1
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef None
|
|
||||||
* @property {typeof isSome} isSome
|
|
||||||
* @property {typeof isNone} isNone
|
|
||||||
* @property {typeof chain} chain
|
|
||||||
* @property {typeof map} map
|
|
||||||
* @property {typeof alt} alt
|
|
||||||
* @property {typeof fold} fold
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @this {Option<T>}
|
|
||||||
* @param {Morphism<T, U>} fn
|
|
||||||
* @returns {Option<U>}
|
|
||||||
*/
|
|
||||||
function map(fn) {
|
|
||||||
return this[Self](this.chain(fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U
|
|
||||||
* @this {Option<T>}
|
|
||||||
* @param {Morphism<T, U>} fn
|
|
||||||
* @param {U} acc
|
|
||||||
* @return {U}
|
|
||||||
*/
|
|
||||||
function fold(fn, acc) {
|
|
||||||
const result = this.map(fn)
|
|
||||||
return result.isSome() ? result[Value] : acc
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @this {Option<T>}
|
|
||||||
* @param {Option<T>} other
|
|
||||||
* @returns {Option<T>}
|
|
||||||
*/
|
|
||||||
function alt(other) {
|
|
||||||
return this.isSome() ? this : other
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @this {Option<T>}
|
|
||||||
* @returns {this is Some<T>}
|
|
||||||
*/
|
|
||||||
function isSome() {
|
|
||||||
return this[Self] === Some
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @this {Option<T>}
|
|
||||||
* @returns {this is None}
|
|
||||||
*/
|
|
||||||
function isNone() {
|
|
||||||
return this === None
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {?T} value
|
|
||||||
* @returns {Some<T>}
|
* @returns {Some<T>}
|
||||||
*/
|
*/
|
||||||
export const Some = value => ({
|
static of(value) {
|
||||||
[Self]: Some,
|
return new Some(value)
|
||||||
[Value]: value,
|
}
|
||||||
isSome,
|
|
||||||
isNone,
|
|
||||||
chain,
|
|
||||||
map,
|
|
||||||
alt,
|
|
||||||
fold
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @returns {R}
|
||||||
|
*/
|
||||||
|
chain(f) {
|
||||||
|
return f(this.#value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @returns {Some<R>}
|
||||||
|
*/
|
||||||
|
map(f) {
|
||||||
|
return Some.of(this.chain(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @returns {Some<R>}
|
||||||
|
*/
|
||||||
|
then(f) {
|
||||||
|
return this.map(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {(acc: U, value: T) => U} f
|
||||||
|
* @param {U} init
|
||||||
|
* @returns {U}
|
||||||
|
*/
|
||||||
|
reduce(f, init) {
|
||||||
|
return f(init, this.#value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
class None extends Algebra(Monad, Foldable) {
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} _f
|
||||||
* @returns {None}
|
* @returns {None}
|
||||||
*/
|
*/
|
||||||
export const None = () => None
|
chain(_f) {
|
||||||
None.isSome = isSome
|
return this
|
||||||
None.isNone = isNone
|
}
|
||||||
None.chain = constant
|
|
||||||
None.map = constant
|
/**
|
||||||
None.alt = alt
|
* @template R
|
||||||
None.fold = fold
|
* @param {Morphism<T, R>} _f
|
||||||
|
* @returns {None}
|
||||||
|
*/
|
||||||
|
map(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} _f
|
||||||
|
* @returns {None}
|
||||||
|
*/
|
||||||
|
then(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {(acc: U, value: T) => U} _f
|
||||||
|
* @param {U} init
|
||||||
|
* @returns {U}
|
||||||
|
*/
|
||||||
|
reduce(_f, init) {
|
||||||
|
return init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
* @param {?T} value
|
* @type {Some<T> | None<T>} Option
|
||||||
* @returns {Option<T>}
|
|
||||||
*/
|
*/
|
||||||
export const Option = value => Some(value)
|
export class Option {
|
||||||
Option.of = Option
|
/**
|
||||||
Option.zero = None
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
*/
|
||||||
|
static of(value) {
|
||||||
|
return Some.of(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T} value
|
||||||
|
*/
|
||||||
|
export const some = value => new Some(value)
|
||||||
|
|
||||||
|
export const none = new None()
|
||||||
|
|
||||||
|
|
|
@ -1,147 +1,175 @@
|
||||||
import { chain, constant, Self, Value } from './common.js'
|
import { Algebra, Foldable, Functor, Monad } from './index.js'
|
||||||
|
|
||||||
/** @import { Construct, Morphism, Set } from './common.js' */
|
/** @import { Morphism } from './types.js' */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T, E
|
* @template T, E
|
||||||
* @typedef {Ok<T> | Err<E>} Result
|
|
||||||
*/
|
*/
|
||||||
|
export class Ok extends Algebra(Monad, Foldable) {
|
||||||
|
/** @type {T} */
|
||||||
|
#value
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
|
||||||
* @typedef OkMethods
|
|
||||||
* @property {typeof isOk} isOk
|
|
||||||
* @property {typeof isErr} isErr
|
|
||||||
* @property {typeof chain} chain
|
|
||||||
* @property {typeof map} map
|
|
||||||
* @property {typeof alt} alt
|
|
||||||
* @property {typeof fold} fold
|
|
||||||
* @property {typeof bimap} bimap
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {Set<T> & Construct<T> & OkMethods<T>} Ok
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef ErrMethods
|
|
||||||
* @property {typeof isOk} isOk
|
|
||||||
* @property {typeof isErr} isErr
|
|
||||||
* @property {typeof chain} chain
|
|
||||||
* @property {typeof map} map
|
|
||||||
* @property {typeof alt} alt
|
|
||||||
* @property {typeof fold} fold
|
|
||||||
* @property {typeof bimap} bimap
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @typedef {Set<T> & Construct<T> & ErrMethods<T>} Err
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U, E
|
|
||||||
* @this {Result<T, E>}
|
|
||||||
* @param {Morphism<T, U>} fn
|
|
||||||
* @returns {Result<U, E>}
|
|
||||||
*/
|
|
||||||
function map(fn) {
|
|
||||||
return /** @type {Result<U, E>} */ (this[Self](this.chain(fn)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T1, T2, E1, E2
|
|
||||||
* @this {Result<T1, E1>}
|
|
||||||
* @param {Morphism<T1, T2>} y
|
|
||||||
* @param {Morphism<E1, E2>} x
|
|
||||||
* @returns {Result<T2, E2>}
|
|
||||||
*/
|
|
||||||
function bimap(x, y) {
|
|
||||||
return map.call(this, y).map(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, U, E
|
|
||||||
* @this {Result<T, E>}
|
|
||||||
* @param {Morphism<T, U>} fn
|
|
||||||
* @param {U} acc
|
|
||||||
* @return {U}
|
|
||||||
*/
|
|
||||||
function fold(fn, acc) {
|
|
||||||
const result = this.map(fn)
|
|
||||||
return result.isOk() ? result[Value] : acc
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @this {Result<T, E>}
|
|
||||||
* @param {Result<T, E>} other
|
|
||||||
* @returns {Result<T, E>}
|
|
||||||
*/
|
|
||||||
function alt(other) {
|
|
||||||
return this.isOk() ? this : other
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @this {Result<T, E>}
|
|
||||||
* @returns {this is Ok<T>}
|
|
||||||
*/
|
|
||||||
function isOk() {
|
|
||||||
return this[Self] === Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @this {Result<T, E>}
|
|
||||||
* @returns {this is Err<E>}
|
|
||||||
*/
|
|
||||||
function isErr() {
|
|
||||||
return this[Self] === Err
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {?T} value
|
|
||||||
* @returns {Ok<T>}
|
|
||||||
*/
|
|
||||||
export const Ok = value => Object.freeze({
|
|
||||||
[Self]: Ok,
|
|
||||||
[Value]: value,
|
|
||||||
isOk,
|
|
||||||
isErr,
|
|
||||||
chain,
|
|
||||||
map,
|
|
||||||
alt,
|
|
||||||
fold,
|
|
||||||
bimap
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {T?} value
|
|
||||||
* @returns {Err<T>}
|
|
||||||
*/
|
|
||||||
export const Err = value => Object.freeze({
|
|
||||||
[Self]: Err,
|
|
||||||
[Value]: value,
|
|
||||||
chain: constant,
|
|
||||||
map: constant,
|
|
||||||
alt,
|
|
||||||
isOk,
|
|
||||||
isErr,
|
|
||||||
fold,
|
|
||||||
bimap
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T, E
|
|
||||||
* @param {T} value
|
* @param {T} value
|
||||||
* @returns {Result<T, E>}
|
* @constructs {Ok<T, E>}
|
||||||
*/
|
*/
|
||||||
export const Result = value => Ok(value)
|
constructor(value) {
|
||||||
Result.of = Result
|
super()
|
||||||
Result.zero = () => Err(undefined)
|
this.#value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {this is Ok<T, E>} */
|
||||||
|
isOk() { return true }
|
||||||
|
|
||||||
|
/** @returns {this is Err<T, E>} */
|
||||||
|
isErr() { return false }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @this {Ok<T, E>}
|
||||||
|
* @returns {R}
|
||||||
|
*/
|
||||||
|
chain(f) {
|
||||||
|
return f(this.#value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @this {Ok<T, E>}
|
||||||
|
* @returns {Ok<R, E>}
|
||||||
|
*/
|
||||||
|
map(f) {
|
||||||
|
return Result.of(this.chain(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} f
|
||||||
|
* @this {Ok<T, E>}
|
||||||
|
* @returns {Ok<R, E>}
|
||||||
|
*/
|
||||||
|
then(f) {
|
||||||
|
return this.map(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<E, R>} _f
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
catch(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {(acc: U, value: T) => U} f
|
||||||
|
* @param {U} init
|
||||||
|
* @returns {U}
|
||||||
|
*/
|
||||||
|
reduce(f, init) {
|
||||||
|
return f(init, this.#value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T, E
|
||||||
|
*/
|
||||||
|
export class Err extends Algebra(Functor, Monad) {
|
||||||
|
/** @type {E} */
|
||||||
|
#value
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {E} value
|
||||||
|
* @constructs {Err<T, E>}
|
||||||
|
*/
|
||||||
|
constructor(value) {
|
||||||
|
super()
|
||||||
|
this.#value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {this is Ok<T, E>} */
|
||||||
|
isOk() { return false }
|
||||||
|
|
||||||
|
/** @returns {this is Err<T, E>} */
|
||||||
|
isErr() { return true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} _f
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
chain(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} _f
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
map(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<T, R>} _f
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
then(_f) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @param {Morphism<E, R>} f
|
||||||
|
* @returns {Err<T, R>}
|
||||||
|
*/
|
||||||
|
catch(f) {
|
||||||
|
return new Err(f(this.#value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template U
|
||||||
|
* @param {(acc: U, value: T) => U} _f
|
||||||
|
* @param {U} init
|
||||||
|
* @returns {U}
|
||||||
|
*/
|
||||||
|
reduce(_f, init) {
|
||||||
|
return init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
* @returns {Ok<T, E>}
|
||||||
|
*/
|
||||||
|
export const ok = v => new Ok(v)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T, E
|
||||||
|
* @param {E} e
|
||||||
|
* @returns {Err<T, E>}
|
||||||
|
*/
|
||||||
|
export const err = e => new Err(e)
|
||||||
|
|
||||||
|
|
33
src/algebra/types.js
Normal file
33
src/algebra/types.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
export default {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T, R
|
||||||
|
* @typedef {(value: T) => R} Morphism
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {<R>(value: T) => R} InferredMorphism
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(value: T) => boolean} Predicate
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(value: T) => Chain<T>} ChainConstructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {<R>(f: Morphism<T, R>) => R} chain
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {{ chain: chain<T> }} Chain
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @typedef {(...args: any[]) => any} Fn */
|
256
src/mixin.js
Normal file
256
src/mixin.js
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
/**
|
||||||
|
* mixwith.js
|
||||||
|
* Author: Justin Fagnani (https://github.com/justinfagnani/)
|
||||||
|
* https://github.com/justinfagnani/mixwith.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
// used by apply() and isApplicationOf()
|
||||||
|
const _appliedMixin = '__mixwith_appliedMixin'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns a subclass of its argument.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const M = (superclass) => class extends superclass {
|
||||||
|
* getMessage() {
|
||||||
|
* return "Hello"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @typedef {Function} MixinFunction
|
||||||
|
* @param {Function} superclass
|
||||||
|
* @return {Function} A subclass of `superclass`
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies `mixin` to `superclass`.
|
||||||
|
*
|
||||||
|
* `apply` stores a reference from the mixin application to the unwrapped mixin
|
||||||
|
* to make `isApplicationOf` and `hasMixin` work.
|
||||||
|
*
|
||||||
|
* This function is usefull for mixin wrappers that want to automatically enable
|
||||||
|
* {@link hasMixin} support.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const Applier = (mixin) => wrap(mixin, (superclass) => apply(superclass, mixin))
|
||||||
|
*
|
||||||
|
* // M now works with `hasMixin` and `isApplicationOf`
|
||||||
|
* const M = Applier((superclass) => class extends superclass {})
|
||||||
|
*
|
||||||
|
* class C extends M(Object) {}
|
||||||
|
* let i = new C()
|
||||||
|
* hasMixin(i, M) // true
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {Function} superclass A class or constructor function
|
||||||
|
* @param {MixinFunction} mixin The mixin to apply
|
||||||
|
* @return {Function} A subclass of `superclass` produced by `mixin`
|
||||||
|
*/
|
||||||
|
export const apply = (superclass, mixin) => {
|
||||||
|
let application = mixin(superclass)
|
||||||
|
application.prototype[_appliedMixin] = unwrap(mixin)
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` iff `proto` is a prototype created by the application of
|
||||||
|
* `mixin` to a superclass.
|
||||||
|
*
|
||||||
|
* `isApplicationOf` works by checking that `proto` has a reference to `mixin`
|
||||||
|
* as created by `apply`.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {Object} proto A prototype object created by {@link apply}.
|
||||||
|
* @param {MixinFunction} mixin A mixin function used with {@link apply}.
|
||||||
|
* @return {boolean} whether `proto` is a prototype created by the application of
|
||||||
|
* `mixin` to a superclass
|
||||||
|
*/
|
||||||
|
export const isApplicationOf = (proto, mixin) =>
|
||||||
|
proto.hasOwnProperty(_appliedMixin) && proto[_appliedMixin] === unwrap(mixin)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` iff `o` has an application of `mixin` on its prototype
|
||||||
|
* chain.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {Object} o An object
|
||||||
|
* @param {MixinFunction} mixin A mixin applied with {@link apply}
|
||||||
|
* @return {boolean} whether `o` has an application of `mixin` on its prototype
|
||||||
|
* chain
|
||||||
|
*/
|
||||||
|
export const hasMixin = (o, mixin) => {
|
||||||
|
while (o != null) {
|
||||||
|
if (isApplicationOf(o, mixin)) return true
|
||||||
|
o = Object.getPrototypeOf(o)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// used by wrap() and unwrap()
|
||||||
|
const _wrappedMixin = '__mixwith_wrappedMixin'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the function `mixin` to be wrapped by the function `wrapper`, while
|
||||||
|
* allowing properties on `mixin` to be available via `wrapper`, and allowing
|
||||||
|
* `wrapper` to be unwrapped to get to the original function.
|
||||||
|
*
|
||||||
|
* `wrap` does two things:
|
||||||
|
* 1. Sets the prototype of `mixin` to `wrapper` so that properties set on
|
||||||
|
* `mixin` inherited by `wrapper`.
|
||||||
|
* 2. Sets a special property on `mixin` that points back to `mixin` so that
|
||||||
|
* it can be retreived from `wrapper`
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin A mixin function
|
||||||
|
* @param {MixinFunction} wrapper A function that wraps {@link mixin}
|
||||||
|
* @return {MixinFunction} `wrapper`
|
||||||
|
*/
|
||||||
|
export const wrap = (mixin, wrapper) => {
|
||||||
|
Object.setPrototypeOf(wrapper, mixin)
|
||||||
|
if (!mixin[_wrappedMixin]) {
|
||||||
|
mixin[_wrappedMixin] = mixin
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwraps the function `wrapper` to return the original function wrapped by
|
||||||
|
* one or more calls to `wrap`. Returns `wrapper` if it's not a wrapped
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} wrapper A wrapped mixin produced by {@link wrap}
|
||||||
|
* @return {MixinFunction} The originally wrapped mixin
|
||||||
|
*/
|
||||||
|
export const unwrap = (wrapper) => wrapper[_wrappedMixin] || wrapper
|
||||||
|
|
||||||
|
const _cachedApplications = '__mixwith_cachedApplications'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates `mixin` so that it caches its applications. When applied multiple
|
||||||
|
* times to the same superclass, `mixin` will only create one subclass, memoize
|
||||||
|
* it and return it for each application.
|
||||||
|
*
|
||||||
|
* Note: If `mixin` somehow stores properties its classes constructor (static
|
||||||
|
* properties), or on its classes prototype, it will be shared across all
|
||||||
|
* applications of `mixin` to a super class. It's reccomended that `mixin` only
|
||||||
|
* access instance state.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin The mixin to wrap with caching behavior
|
||||||
|
* @return {MixinFunction} a new mixin function
|
||||||
|
*/
|
||||||
|
export const Cached = (mixin) => wrap(mixin, (superclass) => {
|
||||||
|
// Get or create a symbol used to look up a previous application of mixin
|
||||||
|
// to the class. This symbol is unique per mixin definition, so a class will have N
|
||||||
|
// applicationRefs if it has had N mixins applied to it. A mixin will have
|
||||||
|
// exactly one _cachedApplicationRef used to store its applications.
|
||||||
|
|
||||||
|
let cachedApplications = superclass[_cachedApplications]
|
||||||
|
if (!cachedApplications) {
|
||||||
|
cachedApplications = superclass[_cachedApplications] = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
let application = cachedApplications.get(mixin)
|
||||||
|
if (!application) {
|
||||||
|
application = mixin(superclass)
|
||||||
|
cachedApplications.set(mixin, application)
|
||||||
|
}
|
||||||
|
|
||||||
|
return application
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates `mixin` so that it only applies if it's not already on the
|
||||||
|
* prototype chain.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin The mixin to wrap with deduplication behavior
|
||||||
|
* @return {MixinFunction} a new mixin function
|
||||||
|
*/
|
||||||
|
export const DeDupe = (mixin) => wrap(mixin, (superclass) =>
|
||||||
|
(hasMixin(superclass.prototype, mixin))
|
||||||
|
? superclass
|
||||||
|
: mixin(superclass))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds [Symbol.hasInstance] (ES2015 custom instanceof support) to `mixin`.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin The mixin to add [Symbol.hasInstance] to
|
||||||
|
* @return {MixinFunction} the given mixin function
|
||||||
|
*/
|
||||||
|
export const HasInstance = (mixin) => {
|
||||||
|
if (Symbol && Symbol.hasInstance && !mixin[Symbol.hasInstance]) {
|
||||||
|
Object.defineProperty(mixin, Symbol.hasInstance, {
|
||||||
|
value(o) {
|
||||||
|
return hasMixin(o, mixin)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mixin
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic mixin decorator that applies the mixin with {@link apply} so that it
|
||||||
|
* can be used with {@link isApplicationOf}, {@link hasMixin} and the other
|
||||||
|
* mixin decorator functions.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin The mixin to wrap
|
||||||
|
* @return {MixinFunction} a new mixin function
|
||||||
|
*/
|
||||||
|
export const BareMixin = (mixin) => wrap(mixin, (s) => apply(s, mixin))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates a mixin function to add deduplication, application caching and
|
||||||
|
* instanceof support.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {MixinFunction} mixin The mixin to wrap
|
||||||
|
* @return {MixinFunction} a new mixin function
|
||||||
|
*/
|
||||||
|
export const Mixin = (mixin) => DeDupe(Cached(BareMixin(mixin)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fluent interface to apply a list of mixins to a superclass.
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* class X extends mix(Object).with(A, B, C) {}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The mixins are applied in order to the superclass, so the prototype chain
|
||||||
|
* will be: X->C'->B'->A'->Object.
|
||||||
|
*
|
||||||
|
* This is purely a convenience function. The above example is equivalent to:
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* class X extends C(B(A(Object))) {}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @param {Function} [superclass=Object]
|
||||||
|
* @return {MixinBuilder}
|
||||||
|
*/
|
||||||
|
export const mix = (superclass) => new MixinBuilder(superclass)
|
||||||
|
|
||||||
|
class MixinBuilder {
|
||||||
|
|
||||||
|
constructor(superclass) {
|
||||||
|
this.superclass = superclass || class { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies `mixins` in order to the superclass given to `mix()`.
|
||||||
|
*
|
||||||
|
* @param {Array.<Mixin>} mixins
|
||||||
|
* @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied
|
||||||
|
*/
|
||||||
|
with(...mixins) {
|
||||||
|
return mixins.reduce((c, m) => m(c), this.superclass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
src/union.js
14
src/union.js
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template R
|
* @template R
|
||||||
* @callback match
|
* @callback cata
|
||||||
* @param {Record<string, Fn<R>> & Partial<Record<'_', EmptyFn<R>>>} pattern
|
* @param {Record<string, Fn<R>> & Partial<Record<'_', EmptyFn<R>>>} pattern
|
||||||
* @returns {R}
|
* @returns {R}
|
||||||
*
|
*
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Variant
|
* @typedef Variant
|
||||||
* @property {match<any>} match
|
* @property {cata<any>} cata
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,21 +39,21 @@
|
||||||
|
|
||||||
const Tag = Symbol('Tag')
|
const Tag = Symbol('Tag')
|
||||||
|
|
||||||
class MatchError extends Error {
|
export class CatamorphismError extends Error {
|
||||||
/** @param {PropertyKey} name */
|
/** @param {PropertyKey} name */
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
super(`unmatched arm in match: ${name.toString()}`)
|
super(`unmatched arm in catamorphism: ${name.toString()}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PropertyKey} variant
|
* @param {PropertyKey} type
|
||||||
* @param {PropertyKey} variant
|
* @param {PropertyKey} variant
|
||||||
* @returns {(...values: any[]) => Variant}
|
* @returns {(...values: any[]) => Variant}
|
||||||
*/
|
*/
|
||||||
const Variant = (type, variant) => (...values) => ({
|
const Variant = (type, variant) => (...values) => ({
|
||||||
[Tag]: type,
|
[Tag]: type,
|
||||||
match(pattern) {
|
cata(pattern) {
|
||||||
if (variant in pattern) {
|
if (variant in pattern) {
|
||||||
// NOTE: this is a workaround for typescript not recognizing
|
// NOTE: this is a workaround for typescript not recognizing
|
||||||
// that objects can be indexed with symbols
|
// that objects can be indexed with symbols
|
||||||
|
@ -61,7 +61,7 @@ const Variant = (type, variant) => (...values) => ({
|
||||||
} else if ('_' in pattern) {
|
} else if ('_' in pattern) {
|
||||||
return pattern._()
|
return pattern._()
|
||||||
} else {
|
} else {
|
||||||
throw new MatchError(variant)
|
throw new CatamorphismError(variant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue