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"]
|
||||
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": {
|
||||
"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,
|
||||
"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==",
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"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' */
|
||||
|
||||
|
||||
|
@ -24,7 +24,6 @@ export class Pure {
|
|||
* @returns {B}
|
||||
*/
|
||||
chain(f) {
|
||||
console.log('Pure.chain', this.#value)
|
||||
return f(this.#value)
|
||||
}
|
||||
|
||||
|
@ -34,7 +33,6 @@ export class Pure {
|
|||
* @returns {Free<B>}
|
||||
*/
|
||||
map(f) {
|
||||
console.log(`Pure.map ${f} ${this.#value}`)
|
||||
// @ts-ignore
|
||||
return pure(f(this.#value))
|
||||
}
|
||||
|
@ -45,7 +43,6 @@ export class Pure {
|
|||
* @returns {Free<C>}
|
||||
*/
|
||||
ap(b) {
|
||||
console.log('Pure.ap', b, this.#value)
|
||||
return b.map(this.#value)
|
||||
}
|
||||
|
||||
|
@ -60,7 +57,6 @@ export class Pure {
|
|||
* @returns {B}
|
||||
*/
|
||||
reduce(f, init) {
|
||||
console.log('Pure.reduce', init, this.#value)
|
||||
return f(init, this.#value)
|
||||
}
|
||||
|
||||
|
@ -88,15 +84,10 @@ export class Impure {
|
|||
* @returns {B}
|
||||
*/
|
||||
chain(f) {
|
||||
console.log('Impure.chain')
|
||||
// @ts-ignore
|
||||
return new Impure(
|
||||
// @ts-ignore
|
||||
() => {
|
||||
console.log(`Impure.chain<${f}>`, this.#next())
|
||||
this.#next().chain(f)
|
||||
}
|
||||
//() => this.#next().chain(f)
|
||||
() => this.#next().chain(f)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -106,14 +97,9 @@ export class Impure {
|
|||
* @returns {Free<B>}
|
||||
*/
|
||||
map(f) {
|
||||
console.log(`Impure.map ${f}`)
|
||||
return new Impure(
|
||||
// @ts-ignore
|
||||
() => {
|
||||
console.log(`Impure.map<${f}>`, this.#next())
|
||||
return this.#next().map(f)
|
||||
}
|
||||
//() => this.#next().map(f)
|
||||
() => this.#next().map(f)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -123,7 +109,6 @@ export class Impure {
|
|||
* @returns {Free<B>}
|
||||
*/
|
||||
ap(b) {
|
||||
console.log('Impure.ap', b)
|
||||
return new Impure(
|
||||
// @ts-ignore
|
||||
() => b.map(this.#next())
|
||||
|
@ -148,45 +133,13 @@ export class Impure {
|
|||
}
|
||||
|
||||
const sequence = (of, iter) => {
|
||||
console.log(ap(of([]), 1))
|
||||
return reduce((acc, x) => {
|
||||
ap(acc, map(prepend, x))
|
||||
}, 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)
|
||||
|
||||
/**
|
||||
* @template A, B
|
||||
* @param {Computation<A, B>} f
|
||||
*/
|
||||
export const impure = f => new Impure(f)
|
||||
|
||||
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'
|
||||
|
||||
/** @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')
|
||||
export * from './option.js'
|
||||
export * from './result.js'
|
||||
export * from './list.js'
|
||||
export * from './free.js'
|
||||
export * from './io.js'
|
||||
export * from './reader.js'
|
||||
|
||||
|
|
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' */
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Pure, Suspend } from './free.js'
|
||||
import { Algebra, AlgebraWithBase, Comonad, Foldable, Monoid, Semigroup } from './index.js'
|
||||
import { Algebra, Comonad, Foldable, Monoid, Semigroup } from './interfaces.js'
|
||||
|
||||
/** @import { InferredMorphism, Morphism } from './types.js' */
|
||||
|
||||
|
@ -188,15 +187,5 @@ export class List {
|
|||
|
||||
const empty = new Empty()
|
||||
|
||||
/**
|
||||
* @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)
|
||||
export const list = value => List.of(value)
|
||||
|
||||
|
|
|
@ -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' */
|
||||
|
||||
|
@ -66,7 +66,7 @@ export class Some extends Algebra(Monoid, Monad, Foldable) {
|
|||
}
|
||||
|
||||
/** @template T */
|
||||
class None extends Algebra(Monoid, Monad, Foldable) {
|
||||
export class None extends Algebra(Monoid, Monad, Foldable) {
|
||||
/**
|
||||
* @template R
|
||||
* @param {Morphism<T, R>} _f
|
||||
|
@ -132,6 +132,5 @@ export class Option {
|
|||
* @param {T} value
|
||||
*/
|
||||
export const some = value => new Some(value)
|
||||
|
||||
export const none = new None()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { id } from './fn.js'
|
||||
import { Algebra, Monad } from './index.js'
|
||||
import { id } from '../../vendor/izuna/src/index.js'
|
||||
import { Algebra, Monad } from './interfaces.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' */
|
||||
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './algebra/index.js'
|
||||
export * from './curry.js'
|
||||
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { TerminalRunner } from 'folktest'
|
||||
import * as Tests from './units/index.js'
|
||||
|
||||
const ap = f => f()
|
||||
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)
|
||||
console.log(TerminalRunner(Tests).toString())
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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) => {
|
||||
assertEq(M(a).bind(f), f(a))
|
||||
|
@ -21,7 +21,7 @@ export const prove = (M, m, a, f, g) => {
|
|||
|
||||
export const Tests = [
|
||||
it('should adhere to monadic laws', () => {
|
||||
[Identity, Constant, Ok, Err, Some, None].forEach(ctr => {
|
||||
[Ok, Err, Some, None].forEach(ctr => {
|
||||
prove(
|
||||
ctr,
|
||||
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