diff --git a/README.md b/README.md deleted file mode 100644 index 96026c3..0000000 --- a/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# kojima -a small functional/monad library - -# Usage -## Example -```js -import { Option, Some, None, Result, Ok, Err } from 'kojima' - -const maybe = Option.of('thing') -maybe.isSome() // true -const isnt = maybe.chain(None).map(_x => 'other') // None -isnt.isSome() // false - -const result = Result.of('3:41am') -result.isOk() // true - -result.chain(() => Err(new Error(-Infinity))) - .map(_x => '4:10am') - .chain(Ok) - -result.isErr() // true - -// crimes! -None == None() // true -``` - -## Documentation - -### curry -> (* -> a) -> (* -> a) - -Returns a curried equivalent of the provided function. - -```js -import { curry } from './curry.js' - -const add = (a, b, c) => a + b + c -const curriedAdd = curry(add) -const add1 = curriedAdd(1) -const add3 = add1(2) -const add4 = curriedAdd(2, 2) - -const six = add3(3) // 6 -const eight = add4(2) // 7 -const twelve = curriedAdd(4, 4, 4) // 12 -``` - -### Option -> Option\ = Some\ | None - -Represents a value which may not exist. - - -#### Methods -##### of :: Option f => a -> f a -> Option.of\(value: a) -> Option\ - -Creates a new `Some` from `T` - -```js -const some = Option.of(1) // Some(1) -``` - -##### zero :: Option f => () -> f a -> Option.zero() -> Option\<()\> - -Creates a new `None` - -```js -const none = Option.zero() // None -``` - -##### chain :: Option m => m a ~> (a -> m b) -> m b -> Option\.chain\(fn: (value: T) -> Option\) -> Option\ - -Transform a `Option` into `Option` by applying `fn(T) -> Option`. - -```js -const some = Option.of(1) -const next = some.chain(x => Some(`value ${x}`)) // Some('value 1') -None.chain(x => Some(`value ${x}`)) // None -const none = some.chain(None) // None -none.chain(() => Some(1)) // None -``` - -##### map :: Option f => f a ~> (a -> b) -> f b -> Option\.map\(fn: (value: T) -> U) -> Option\ - -##### alt :: Option f => f a ~> f a -> f a -> Option\.alt(other: Option\) -> Option\ - -Choose between either the first or second `Option` based on existence. - -```js -const some = Option.of(1) -const none = Option.zero() - -some.alt(none) // Some(1) -none.alt(some) // Some(1) -some.alt(Some(2)) // Some(1) -Some(2).alt(some) // Some(2) -none.alt(None) // None -None.alt(none) // None -``` - -##### fold :: Option f => f a ~> ((b, a) -> b, b) -> b -> Option\.fold\(fn: ((acc: U, value: T) -> U, initial: U) -> U) -> U - -Fold over a `Option`, accumulating the value. This is `unwrap_or_default` in Rust. - -```js -const some = Some(1) -some.fold((acc, x) => x), 2) // 1 -some.fold((acc, x) => acc), 2) // 2 - -const none = Option.zero() -none.fold((acc, x) => x, 2) // 2 -``` - -##### isSome :: Option f => () -> boolean -> Option\.isSome() -> boolean - -Returns a boolean based on whether the Option is `Some` - -```js -Some(1).isSome() // true -None.isSome() // false -``` - -##### isNone :: Option f => () -> boolean -> Option\.isNone() -> boolean - -Returns a boolean based on whether the Option is `None` - -```js -Some(1).isNone() // false -None.isNone() // true -``` - - -### Result -> Result = Ok\ | Err\ - -Represents the result of a computation which may fail. - -#### Methods -##### of :: Result f => a -> f a -> Result.of\(value: a) -> Result\ - -Creates a new `Ok` from `T` - -```js -const ok = Result.of(1) // Ok(1) -``` - -##### zero :: Result f => () -> f a -> Result.zero() -> Result\<(), ()\> - -Creates a new `Err<()>` - -```js -const err = Result.zero() // Err<()>() -``` - -##### chain :: Result m => m a ~> (a -> m b) -> m b -> Result\.chain\(fn: (value: T) -> Result) -> Result\ - -Transform a `Result` into `Result` by applying `fn(T) -> Result`. - -```js -const ok = Result.of(1) -const next = ok.chain(x => Ok(`value ${x}`)) // Ok('value 1') -Err(0).chain(x => Ok(1)) // Err(0) -const err = next.chain(() => Err(0)) // Err(0) -err.chain(() => Ok(1)) // Err(0) -``` - -##### map :: Result f => f a ~> (a -> b) -> f b -> Result\.map\(fn: (value: T) -> U) -> Result\ - -Transform a `Result` into a `Result` by applying `fn(T) -> U` - -```js -const ok = Result.of(1) -const next = ok.map(x => `value ${x}`) // Ok('value 1') -Err(0).map(x => Ok(1)) // Err(0) - -``` - -##### alt :: Result f => f a ~> f a -> f a -> Result\.alt(other: Result\) -> Result\ - -Choose between either the first or second `Result` based on success. - -```js -const ok = Result.of(1) -const err = Result.zero() - -ok.alt(err) // Ok(1) -err.alt(ok) // Ok(1) -ok.alt(Ok(2)) // Ok(1) -Ok(2).alt(ok) // Ok(2) -err.alt(Err(new Error('wont see this'))) // Err<()>() -Err(new Error('hi! :3')).alt(err) // Err(new Error('hi! :3')) -``` - -##### fold :: Result f => f a ~> ((b, a) -> b, b) -> b -> Result\.fold\(fn: ((acc: U, value: T) -> U, initial: U) -> U) -> U - -Fold over a `Result`, accumulating the value. This is `unwrap_or_default` in Rust. - -```js -const ok = Ok(1) -ok.fold((acc, x) => x), 2) // 1 -ok.fold((acc, x) => acc), 2) // 2 - -const err = Result.zero() -err.fold((acc, x) => x, 2) // 2 -``` - -##### bimap :: Result f => f a c ~> (a -> b, c -> d) -> f b d -> Result\.bimap\(x: (value: T1) -> T2, y: (error: E1) -> E2) -> Result\ - -```js -const ok = Ok(1) - -ok.bimap( - x => x + 1, - y => y * 2 -) // Ok(2) - -const err = Err(4) - -err.bimap( - x => x + 1, - y => y * 2 -) // Err(8) -``` - -##### isOk :: Result f => () -> boolean -> Result\.isOk() -> boolean - -Returns a boolean based on whether the Result is `Ok` - -```js -Ok(1).isOk() // true -Err(1).isOk() // false -``` - -##### isErr :: Result f => () -> boolean -> Result\.isErr() -> boolean - -Returns a boolean based on whether the Result is `Err` - -```js -Ok(1).isErr() // false -Err(1).isErr() // true -``` - diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 8d04008..0000000 --- a/dist/index.js +++ /dev/null @@ -1,1372 +0,0 @@ -(() => { - // src/mixin.js - var _appliedMixin = "__mixwith_appliedMixin"; - var apply = (superclass, mixin) => { - let application = mixin(superclass); - application.prototype[_appliedMixin] = unwrap(mixin); - return application; - }; - var isApplicationOf = (proto, mixin) => proto.hasOwnProperty(_appliedMixin) && proto[_appliedMixin] === unwrap(mixin); - var hasMixin = (o, mixin) => { - while (o != null) { - if (isApplicationOf(o, mixin)) return true; - o = Object.getPrototypeOf(o); - } - return false; - }; - var _wrappedMixin = "__mixwith_wrappedMixin"; - var wrap = (mixin, wrapper) => { - Object.setPrototypeOf(wrapper, mixin); - if (!mixin[_wrappedMixin]) { - mixin[_wrappedMixin] = mixin; - } - return wrapper; - }; - var unwrap = (wrapper) => wrapper[_wrappedMixin] || wrapper; - var _cachedApplications = "__mixwith_cachedApplications"; - var Cached = (mixin) => wrap(mixin, (superclass) => { - let cachedApplications = superclass[_cachedApplications]; - if (!cachedApplications) { - cachedApplications = superclass[_cachedApplications] = /* @__PURE__ */ new Map(); - } - let application = cachedApplications.get(mixin); - if (!application) { - application = mixin(superclass); - cachedApplications.set(mixin, application); - } - return application; - }); - var DeDupe = (mixin) => wrap(mixin, (superclass) => hasMixin(superclass.prototype, mixin) ? superclass : mixin(superclass)); - var BareMixin = (mixin) => wrap(mixin, (s) => apply(s, mixin)); - var Mixin = (mixin) => DeDupe(Cached(BareMixin(mixin))); - var mix = (superclass) => new MixinBuilder(superclass); - var MixinBuilder = class { - constructor(superclass) { - this.superclass = superclass || class { - }; - } - /** - * Applies `mixins` in order to the superclass given to `mix()`. - * - * @param {Array.} mixins - * @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied - */ - with(...mixins) { - return mixins.reduce((c, m) => m(c), this.superclass); - } - }; - - // src/algebra/interfaces.js - var ProtectedConstructor = Symbol("ProtectedConstructor"); - var NotImplementedError = class extends Error { - /** @param {string} name */ - constructor(name) { - super(`${name} is not implemented`); - } - }; - var MethodType = Object.freeze({ - Instance: 0, - Static: 1 - }); - var Method = 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 err2 = new NotImplementedError( - `${interfaceName}::${this.#name}` - ); - return function() { - throw err2; - }; - } - /** - * @param {Function} target - */ - _getInstallationPoint(target) { - switch (this.#type) { - case 0: - return Object.getPrototypeOf(target); - 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; - } - }; - var Implementations = Symbol(); - var Interface = class { - #name; - /** @type {MixinFunction} */ - #mixin; - /** @type {Set} */ - #methods = /* @__PURE__ */ new Set(); - /** @type {Set} */ - #interfaces = /* @__PURE__ */ new Set(); - /** @param {string} name */ - constructor(name) { - this.#name = name; - this.#mixin = Mixin(this.build.bind(this)); - } - get name() { - return this.#name; - } - findInterfaces(type) { - let interfaces = /* @__PURE__ */ new Set(); - let current = type; - while (current != null) { - interfaces = new Set(current[Implementations]).union(interfaces); - current = Object.getPrototypeOf(current); - } - return interfaces; - } - /** - * @param {{ name: string }} type - * @returns {boolean} - */ - implementedBy(type) { - return this.findInterfaces(type).has(this); - } - /** - * @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 {MixinFunction} - */ - asMixin() { - return this.#mixin; - } - /** - * @param {FunctionConstructor} base - */ - build(base) { - const interfaces = [...this.#interfaces.values()]; - const mixins = interfaces.map((x) => x.asMixin()); - const Interfaces5 = mix(base).with(...mixins); - const Algebra2 = class extends Interfaces5 { - }; - for (const method of this.#methods) { - method.implement(this, Algebra2); - } - const prototype = Object.getPrototypeOf(Algebra2); - prototype[Implementations] = new Set(interfaces); - return Algebra2; - } - }; - var BaseSet = class { - }; - var AlgebraWithBase = (base) => (...algebras) => { - return mix(base).with(...algebras.map((x) => x.asMixin())); - }; - var Algebra = AlgebraWithBase(BaseSet); - var Setoid = new Interface("Setoid").specifies("equals"); - var Ord = new Interface("Ord").specifies("lte"); - var Semigroupoid = new Interface("Semigroupoid").specifies("compose"); - var Category = new Interface("Category").extends(Semigroupoid).specifies( - Method.from("id").isStatic() - ); - var Semigroup = new Interface("Semigroup").specifies("concat"); - var Monoid = new Interface("Monoid").extends(Semigroup).specifies( - Method.from("empty").isStatic() - ); - var Group = new Interface("Group").extends(Monoid).specifies("invert"); - var Filterable = new Interface("Filterable").specifies("filter"); - var Functor = new Interface("Functor").specifies("map"); - var Contravariant = new Interface("Contravariant").specifies("contramap"); - var Apply = new Interface("Apply").extends(Functor).specifies("ap"); - var Applicative = new Interface("Applicative").extends(Apply).specifies( - Method.from("of").isStatic() - ); - var Alt = new Interface("Alt").extends(Functor).specifies("alt"); - var Plus = new Interface("Plus").extends(Alt).specifies( - Method.from("zero").isStatic() - ); - var Alternative = new Interface("Alternative").extends(Applicative, Plus); - var Foldable = new Interface("Foldable").specifies("fold"); - var Traversable = new Interface("Traversable").extends(Functor, Foldable).specifies("traverse"); - var Chain = new Interface("Chain").extends(Apply).specifies( - Method.from("chain").implementation(function(f) { - return f(this._value); - }) - ); - var ChainRef = new Interface("ChainRec").extends(Chain).specifies( - Method.from("chainRec").isStatic() - ); - var Monad = new Interface("Monad").extends(Applicative, Chain); - var Extend = new Interface("Extend").extends(Functor).specifies("extend"); - var Comonad = new Interface("Comonad").extends(Extend).specifies("extract"); - var Bifunctor = new Interface("Bifunctor").extends(Functor).specifies("bimap"); - var Profunctor = new Interface("Profunctor").extends(Functor).specifies("promap"); - - // src/algebra/identity.js - var Identity = class extends Algebra(Monad, Comonad) { - #value; - /** - * @param {T} value - */ - constructor(value) { - super(); - this.#value = value; - } - /** - * @template T - * @param {T} value - */ - static of(value) { - return id(value); - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Identity} - */ - map(f) { - return id(f(this.#value)); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Apply>} b - * @returns {Apply} - */ - ap(b) { - return ( - /** @type {Apply} */ - b.map((f) => f(this.#value)) - ); - } - /** - * @template U - * @param {Morphism>} f - */ - chain(f) { - return f(this.#value); - } - /** - * @param {(value: Identity) => T} f - */ - extend(f) { - return id(f(this)); - } - extract() { - return this.#value; - } - toString() { - return `Identity(${this.#value})`; - } - }; - var id = (value) => new Identity(value); - - // src/algebra/option.js - var Interfaces = Algebra(Setoid, Alternative, Monad, Foldable); - var Some = class _Some extends Interfaces { - /** @type {T} */ - #value; - /** @param {T} value */ - constructor(value) { - super(); - this.#value = value; - } - /** - * @type {SetoidT['equals']} - * @param {Some} other - */ - equals(other) { - if (other instanceof _Some) { - return false; - } - const eq = ( - /** @type {Some} */ - other.chain((v) => id(v === this.#value)) - ); - return ( - /** @type {Identity} */ - eq.extract() - ); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Some>} b - * @returns {Some} - */ - ap(b) { - return ( - /** @type {Some} */ - b.chain( - (f) => ( - /** @type {Some} */ - this.map(f) - ) - ) - ); - } - /** - * @type {Alt['alt']} - */ - alt(b) { - return this; - } - /** - * @type {Chain['chain']} - */ - chain(f) { - return f(this.#value); - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Some} - */ - map(f) { - return ( - /** @type {Some} */ - this.chain((v) => some(f(v))) - ); - } - /** - * @type {Functor['map']} - */ - then(f) { - return this.map(f); - } - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} f - * @param {U} init - * @returns {U} - */ - reduce(f, init) { - return f(init, this.#value); - } - toString() { - return `Some(${this.#value})`; - } - }; - var None = class extends Interfaces { - /** - * @type {SetoidT['equals']} - */ - equals(other) { - return other === none; - } - /** - * @type {Apply['ap']} - * @returns {this} - */ - ap(_b) { - return this; - } - /** - * @type {Alt['alt']} - */ - alt(b) { - return b; - } - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism} _f - * @returns {this} - */ - chain(_f) { - return this; - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} _f - * @returns {this} - */ - map(_f) { - return this; - } - /** - * @template R - * @type {Functor['map']} - * @param {Morphism} _f - * @returns {None} - */ - then(_f) { - return this; - } - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} _f - * @param {U} init - * @returns {U} - */ - reduce(_f, init) { - return init; - } - toString() { - return "None"; - } - }; - var some = (value) => new Some(value); - var none = new None(); - var TypeRef = some; - TypeRef.constructor["of"] = some; - TypeRef.constructor["zero"] = none; - - // src/algebra/result.js - var Interfaces2 = Algebra(Setoid, Alternative, Monad, Foldable, Bifunctor); - var Ok = class _Ok extends Interfaces2 { - /** @type {T} */ - #value; - /** - * @param {T} value - * @constructs {Ok} - */ - constructor(value) { - super(); - this.#value = value; - } - /** - * @type {SetoidT['equals']} - * @param {Result} other - * @returns {boolean} - */ - equals(other) { - if (!(other instanceof _Ok)) { - return false; - } - const eq = other.chain((v) => id(v === this.#value)); - return ( - /** @type {Identity} */ - eq.extract() - ); - } - /** @returns {this is Ok} */ - isOk() { - return true; - } - /** @returns {this is Err} */ - isErr() { - return false; - } - /** - * @type {Chain['chain']} - */ - chain(f) { - return f(this.#value); - } - /** - * @template E2 - * @type {Chain['chain']} - * @param {Morphism} _f - * @returns {this} - */ - chainErr(_f) { - return this; - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Functor} - */ - map(f) { - return this.chain((v) => ok(f(v))); - } - /** - * @template E2 - * @type {Functor['map']} - * @this {Result} - * @param {Morphism} _f - * @returns {Result} - */ - mapErr(_f) { - return ( - /** @type {never} */ - this - ); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Apply>} b - * @returns {Result} - */ - ap(b) { - return ( - /** @type {Result} */ - this.chain( - (v) => ( - /** @type {Chain} */ - b.map((f) => f(v)) - ) - ) - ); - } - /** - * @type Alt['alt'] - */ - alt(_b) { - return this; - } - /** - * @template U - * @borrows {Result~map} - * @param {Morphism} f - */ - then(f) { - return this.map(f); - } - /** - * @template R - * @param {Morphism} _f - * @returns {this} - */ - catch(_f) { - return this; - } - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} f - * @param {U} init - * @returns {U} - */ - reduce(f, init) { - return f(init, this.#value); - } - /** - * @template T2, E2 - * @type {BifunctorT['bimap']} - * @param {Morphism} f - * @param {Morphism} _g - * @returns {Result} - */ - bimap(f, _g) { - return ( - /** @type {Result} */ - this.map(f) - ); - } - toString() { - return `Ok(${this.#value})`; - } - }; - var Err = class _Err extends Interfaces2 { - /** @type {E} */ - #value; - /** - * @param {E} value - * @constructs {Err} - */ - constructor(value) { - super(); - this.#value = value; - } - /** - * @type {SetoidT['equals']} - * @param {Err} other - * @returns {boolean} - */ - equals(other) { - if (!(other instanceof _Err)) { - return false; - } - const eq = other.chainErr((v) => id(v === this.#value)); - return ( - /** @type {Identity} */ - eq.extract() - ); - } - /** @returns {this is Ok} */ - isOk() { - return false; - } - /** @returns {this is Err} */ - isErr() { - return true; - } - /** - * @type {Chain['chain']} - * @returns {this} - */ - chain(_f) { - return this; - } - /** - * @template E2 - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Result} - */ - chainErr(f) { - return f(this.#value); - } - /** - * @type {Functor['map']} - * @returns {this} - */ - map(_f) { - return this; - } - /** - * @template E2 - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Result} - */ - mapErr(f) { - return ( - /** @type {Result} */ - this.bimap(id, f) - ); - } - /** - * @type {Functor['map']} - * @returns {this} - */ - then(_f) { - return this; - } - /** - * @template R - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Err} - */ - catch(f) { - return new _Err(f(this.#value)); - } - /** - * @type Alt['alt'] - */ - alt(b) { - return b; - } - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} _f - * @param {U} init - * @returns {U} - */ - reduce(_f, init) { - return init; - } - /** - * @template T2, E2 - * @type {BifunctorT['bimap']} - * @param {Morphism} _f - * @param {Morphism} g - * @returns {Result} - */ - bimap(_f, g) { - return ( - /** @type {Result} */ - err(g(this.#value)) - ); - } - toString() { - return `Err(${this.#value})`; - } - }; - var ok = (v) => new Ok(v); - var err = (e) => new Err(e); - var TypeRef2 = ok; - TypeRef2.constructor["of"] = ok; - TypeRef2.constructor["zero"] = err; - - // src/algebra/free.js - var Interfaces3 = Algebra(Monad); - var Pure = class extends Interfaces3 { - #value; - /** - * @param {T} value - */ - constructor(value) { - super(); - this.#value = value; - } - /** - * @template T - * @param {T} value - */ - static of(value) { - return liftF(value); - } - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Pure} - */ - chain(f) { - return f(this.#value); - } - /** - * @type {Functor['map']} - */ - map(f) { - return this.chain((x) => pure(f(x))); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Free>} b - * @returns {Free} - */ - ap(b) { - return ( - /** @type {Free} */ - b.chain( - (f) => ( - /** @type {Chain} */ - this.map(f) - ) - ) - ); - } - interpret() { - return this.#value; - } - toString() { - return `Pure(${this.#value})`; - } - }; - var Impure = class extends Interfaces3 { - #value; - #next; - /** - * @param {T} value - * @param {(value: T) => Free} next - */ - constructor(value, next) { - super(); - this.#value = value; - this.#next = next; - } - /** - * @template T - * @param {T} value - */ - static of(value) { - return liftF(value); - } - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Free} - */ - chain(f) { - return ( - /** @type {Free} */ - impure( - this.#value, - (x) => ( - /** @type {Free} */ - this.#next(x).chain(f) - ) - ) - ); - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Free} - */ - map(f) { - return ( - /** @type {Free} */ - impure( - this.#value, - (x) => ( - /** @type Free} */ - this.#next(x).map(f) - ) - ) - ); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Free>} b - * @returns {Free} - */ - ap(b) { - return ( - /** @type {Free} */ - impure( - this.#value, - (x) => ( - /** @type {Free} */ - b.chain((f) => ( - /** @type {Free} */ - this.#next(x).map(f) - )) - ) - ) - ); - } - interpret() { - return this.#next(this.#value).interpret(); - } - toString() { - return `Impure(${this.#value}, ${this.#next})`; - } - }; - var pure = (value) => new Pure(value); - var impure = (x, f) => new Impure(x, f); - var liftF = (value) => impure(value, pure); - var TypeRef3 = Pure; - TypeRef3.constructor["of"] = Pure; - - // src/algebra/list.js - var Interfaces4 = Algebra(Semigroup, Foldable, Monoid, Comonad); - var Empty = class extends Interfaces4 { - constructor() { - super(); - } - /** - * @template U - * @type {Chain['chain']} - * @this {Empty} - * @param {Morphism>} _f - * @returns {List} - */ - chain(_f) { - return ( - /** @type {List} */ - this - ); - } - /** - * @template U - * @type {Functor['map']} - * @this {Empty} - * @param {Morphism} _f - * @returns {List} - */ - map(_f) { - return this; - } - /** - * @template U - * @type {Apply['ap']} - * @this {Empty} - * @param {List>} _b - * @returns {List} - */ - ap(_b) { - return ( - /** @type {List} */ - this - ); - } - /** - * @type {SemigroupT['concat']} - */ - concat(b) { - return b; - } - /** - * @type {FoldableT['reduce']} - */ - reduce(_f, acc) { - return acc; - } - count() { - return 0; - } - /** @returns {this is Empty} */ - isEmpty() { - return true; - } - toString() { - return `List(Empty)`; - } - }; - var Element = class _Element extends Interfaces4 { - #head; - #tail; - /** @type {List} */ - #cache; - /** - * @param {T} head - * @param {() => List} [tail] - */ - constructor(head, tail = List.empty) { - super(); - this.#head = head; - this.#tail = tail; - this.#cache = null; - } - /** - * @template U - * @type {Chain['chain']} - * @this {Element} - * @param {Morphism>} f - * @returns {List} - */ - chain(f) { - return ( - /** @type {List} */ - f(this.#head).concat( - /** @type {never} */ - this.tail().chain(f) - ) - ); - } - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {List} - */ - map(f) { - return new _Element( - f(this.#head), - () => ( - /** @type {List} */ - this.tail().map(f) - ) - ); - } - /** - * @template U - * @type {Apply['ap']} - * @this {Element} - * @param {List>} b - * @returns {List} - */ - ap(b) { - if (b.isEmpty()) { - return List.empty(); - } - const head = ( - /** @type {List} */ - this.map(b.head()) - ); - const rest = ( - /** @type {List} */ - this.ap(b.tail()) - ); - return ( - /** @type {List} */ - head.concat(rest) - ); - } - /** - * @type {SemigroupT['concat']} - */ - concat(b) { - return new _Element( - this.#head, - () => ( - /** @type {List} */ - this.tail().concat(b) - ) - ); - } - /** - * @type {FoldableT['reduce']} - */ - reduce(f, acc) { - return this.tail().reduce(f, f(acc, this.#head)); - } - head() { - return this.#head; - } - tail() { - return this.#cache || (this.#cache = this.#tail()); - } - count() { - return this.tail().count() + 1; - } - /** @returns {this is Empty} */ - isEmpty() { - return false; - } - toArray() { - return this.reduce( - reduceArray, - [] - ); - } - toString() { - return `List(${this.toArray()})`; - } - }; - var TypeRef4 = class { - /** - * @template T - * @param {T} value - * @returns {List} - */ - static of(value) { - return new Element(value); - } - /** - * @template T - * @param {Iterable} iterable - * @returns {List} - */ - static from(iterable) { - const iterator = Iterator.from(iterable); - return List.fromIterator(iterator); - } - /** - * @template T - * @param {Iterator} iterator - * @returns {List} - */ - static fromIterator(iterator) { - const next = iterator.next(); - if (next.done) { - return List.empty(); - } else { - return new Element(next.value, () => List.fromIterator(iterator)); - } - } - /** - * @template T - * @param {T} head - * @param {List} tail - * @returns {List} - */ - static cons(head, tail) { - return new Element(head, () => tail); - } - static empty() { - return empty; - } - }; - var reduceArray = (acc, x) => acc.concat(x); - var List = TypeRef4; - var empty = new Empty(); - var list = (value) => List.of(value); - - // src/algebra/io.js - var IO = class _IO extends Algebra(Monad) { - _effect; - /** - * @param {T} effect - */ - constructor(effect) { - super(); - this._effect = effect; - } - /** - * @template {Fn} T - * @param {T} a - */ - static of(a) { - return new _IO(() => a); - } - /** - * @template {Fn} U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {IO} - */ - chain(f) { - return ( - /** @type {IO} */ - _IO.of(() => f(this.run()).run()) - ); - } - /** - * @template {Fn} U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {IO} - */ - map(f) { - return ( - /** @type {IO} */ - _IO.of(() => f(this.run())) - ); - } - /** - * @template {Fn} U - * @type {Apply['ap']} - * @param {IO>} other - * @returns {IO} - */ - ap(other) { - return ( - /** @type {IO} */ - _IO.of(() => other.run()(this.run())) - ); - } - run() { - return this._effect(); - } - toString() { - return `IO(${this._effect})`; - } - }; - - // node_modules/izuna/src/curry.js - function curryN(arity, func) { - return function curried(...args) { - if (args.length >= arity) { - return func.apply(this, args); - } else { - return function(...args2) { - return curried.apply(this, args.concat(args2)); - }; - } - }; - } - function curry(func) { - return curryN(func.length, func); - } - - // node_modules/izuna/src/list.js - function isIterable(value) { - return value[Symbol.iterator] != null; - } - function* iter(value) { - if (isIterable(value)) { - yield* Iterator.from(value); - } else { - yield value; - } - } - var concat = function* (...iterators) { - for (const iter2 of iterators) { - for (const item of Iterator.from(iter2)) { - yield item; - } - } - }; - var prepend = curry( - /** - * @template T - * @param {T} x - * @param {Iterable} xs - */ - (x, xs) => concat([x], xs) - ); - - // node_modules/izuna/src/function.js - var id2 = (x) => x; - var compose = curry( - /** - * @template T, U, V - * @param {Morphism} f - * @param {Morphism} g - * @param {T} x - * @returns V - */ - (f, g, x) => chain(g, chain(f, x)) - ); - var liftA = (a) => Array.isArray(a) ? a : [a]; - var liftF2 = curry((binary, x, y) => binary(x, y)); - var ifElse = curry( - /** - * @template T - * @param {Predicate} pred - * @param {InferredMorphism} pass - * @param {InferredMorphism} fail - * @param {T} x - */ - (pred, pass, fail, x) => pred(x) ? pass(x) : fail(x) - ); - var when = curry( - /** - * @template T - * @param {Predicate} pred - * @param {InferredMorphism} pass - * @param {T} x - */ - (pred, pass, x) => ifElse(pred, pass, id2, x) - ); - var unless = curry( - /** - * @template T - * @param {Predicate} pred - * @param {InferredMorphism} fail - * @param {T} x - */ - (pred, fail, x) => ifElse(pred, id2, fail, x) - ); - var ap = curry( - /** - * @template A, B - * @template {Morphism} Ap - * @param {Morphism} f - * @param {{ ap: Ap } | Ap} a - */ - (f, a) => { - const fs = liftA(dispatchF("ap", f)); - const args = liftA(a); - const xs = fs.reduce((acc, f2) => concat(acc, iter(map(f2, args))), []); - return [...xs]; - } - ); - var chain = curry( - function chain2(f, a) { - } - ); - var map = curry((f, a) => { - }); - var reduce = curry((f, acc, xs) => { - }); - - // node_modules/izuna/src/type.js - var is = curry( - /** - * @template T - * @param {FunctionConstructor} ctr - * @param {T} x - */ - (ctr, x) => x.constructor === ctr - ); - var isArray = Array.isArray; - - // src/algebra/reader.js - var Reader = class _Reader extends Algebra(Monad) { - #run; - /** @param {InferredMorphism} run */ - constructor(run) { - super(); - this.#run = run; - } - /** - * @template T - * @type {ApplicativeTypeRef>['of']} - * @param {T} a - * @returns {Reader} - */ - static of(a) { - return new _Reader( - /** @type {InferredMorphism} */ - (_env) => a - ); - } - /** @template T */ - static ask() { - return new _Reader( - /** @type {InferredMorphism} */ - id2 - ); - } - /** - * @type {Functor['map']} - * @param {InferredMorphism} f - * @returns {Reader} - */ - map(f) { - return ( - /** @type {Reader} */ - this.chain((value) => _Reader.of(f(value))) - ); - } - /** - * @type {Chain['chain']} - * @param {InferredMorphism} f - * @returns {Reader} - */ - chain(f) { - return new _Reader((env) => { - const result = this.#run(env); - const next = f(result); - return next.run(env); - }); - } - /** - * @template U - * @type {Apply['ap']} - * @param {Reader>} b - * @returns {Reader} - */ - ap(b) { - return ( - /** @type {Reader} */ - b.chain( - (f) => ( - /** @type {Reader} */ - this.map(f) - ) - ) - ); - } - /** - * @param {InferredMorphism} f - * @returns {Reader} - */ - local(f) { - return new _Reader( - /** @type {InferredMorphism} */ - (env) => this.#run(f(env)) - ); - } - /** - * @template U - * @param {T} env - * @returns {U} - */ - run(env) { - return this.#run(env); - } - }; -})(); diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 4f05d0e..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "es2020", - "target": "es6", - "lib": ["esnext", "dom"], - "checkJs": true, - "paths": { - "/*": ["./*"] - } - }, - "exclude": [ - "node_modules" - ] -} - diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a621b19..0000000 --- a/package-lock.json +++ /dev/null @@ -1,485 +0,0 @@ -{ - "name": "kojima", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "kojima", - "version": "1.0.0", - "license": "GPL-3.0-or-later", - "dependencies": { - "esbuild": "^0.25.2", - "izuna": "git+https://git.kitsu.cafe/rowan/izuna.git", - "typescript": "^5.8.2" - }, - "devDependencies": { - "folktest": "git+https://git.kitsu.cafe/rowan/folktest.git" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "node_modules/folktest": { - "version": "1.0.0", - "resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#708d44f1215be33fcceba426029f44b4f963dbe5", - "dev": true, - "license": "GPL-3.0-or-later" - }, - "node_modules/izuna": { - "version": "1.0.0", - "resolved": "git+https://git.kitsu.cafe/rowan/izuna.git#e11a0870c27eeb5ea1a4ae3fedccca008eda15c2", - "license": "GPL-3.0-or-later" - }, - "node_modules/typescript": { - "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", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -} diff --git a/package.json b/package.json index a096e62..40f6cb7 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,17 @@ { "name": "kojima", - "type": "module", - "author": "Rowan (https://kitsu.cafe)", - "version": "1.0.0", - "description": "", + "version": "2.0.0", "main": "src/index.js", + "type": "module", "scripts": { - "test": "./tests/index.js", - "build": "esbuild ./src/index.js --bundle --outfile=./dist/index.js" + "test": "node --test" }, - "keywords": [ - "functional", - "functional programming", - "fp", - "monad" - ], - "license": "GPL-3.0-or-later", - "devDependencies": { - "folktest": "git+https://git.kitsu.cafe/rowan/folktest.git" + "repository": { + "type": "git", + "url": "git@git.kitsu.cafe:rowan/kojima.git" }, - "dependencies": { - "esbuild": "^0.25.2", - "izuna": "git+https://git.kitsu.cafe/rowan/izuna.git", - "typescript": "^5.8.2" - } + "keywords": [], + "author": "", + "license": "ISC", + "description": "" } diff --git a/src/algebra/free.js b/src/algebra/free.js deleted file mode 100644 index 4add4e8..0000000 --- a/src/algebra/free.js +++ /dev/null @@ -1,177 +0,0 @@ -import { Algebra, Monad } from './interfaces.js' - -/** @import { Applicative, ApplicativeTypeRef, Apply, Chain, Functor, Morphism } from './types.js' */ - -const Interfaces = Algebra(Monad) - -/** - * @template T - * @typedef {Applicative & (Pure | Impure)} Free - */ - -/** - * @template T - * @implements {Applicative} - */ -export class Pure extends Interfaces { - #value - - /** - * @param {T} value - */ - constructor(value) { - super() - this.#value = value - } - - /** - * @template T - * @param {T} value - */ - static of(value) { - return liftF(value) - } - - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Pure} - */ - chain(f) { - return f(this.#value) - } - - /** - * @type {Functor['map']} - */ - map(f) { - return this.chain(x => pure(f(x))) - } - - - /** - * @template U - * @type {Apply['ap']} - * @param {Free>} b - * @returns {Free} - */ - ap(b) { - return /** @type {Free} */ (b.chain(f => - /** @type {Chain} */(this.map(f)) - )) - } - - interpret() { - return this.#value - } - - toString() { - return `Pure(${this.#value})` - } -} - -/** - * @template T - * @implements {Applicative} - */ -export class Impure extends Interfaces { - #value - #next - - /** - * @param {T} value - * @param {(value: T) => Free} next - */ - constructor(value, next) { - super() - this.#value = value - this.#next = next - } - - /** - * @template T - * @param {T} value - */ - static of(value) { - return liftF(value) - } - - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Free} - */ - chain(f) { - return /** @type {Free} */ (impure( - this.#value, - x => /** @type {Free} */(this.#next(x).chain(f)) - )) - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Free} - */ - map(f) { - return /** @type {Free} */ (impure( - this.#value, - x => /** @type Free} */(this.#next(x).map(f)) - )) - } - - /** - * @template U - * @type {Apply['ap']} - * @param {Free>} b - * @returns {Free} - */ - ap(b) { - return /** @type {Free} */ (impure( - this.#value, - x => /** @type {Free} */(b.chain(f => - /** @type {Free} */(this.#next(x).map(f))) - ) - )) - } - - interpret() { - return this.#next(this.#value).interpret() - } - - toString() { - return `Impure(${this.#value}, ${this.#next})` - } -} - - -/** - * @template T - * @param {T} value - */ -export const pure = value => new Pure(value) - -/** - * @template T - * @param {T} x - * @param {(value: T) => Free} f - */ -export const impure = (x, f) => new Impure(x, f) - -/** - * @template T - * @param {T} value - */ -export const liftF = value => impure(value, pure) - -/** - * @template T - * @type {ApplicativeTypeRef>} - */ -export const TypeRef = Pure -TypeRef.constructor['of'] = Pure - - diff --git a/src/algebra/identity.js b/src/algebra/identity.js deleted file mode 100644 index 538e0dd..0000000 --- a/src/algebra/identity.js +++ /dev/null @@ -1,75 +0,0 @@ -import { Algebra, Comonad, Monad } from './interfaces.js' - -/** @import { Apply, Functor, Morphism } from './types.js' */ - -/** @template T */ -export class Identity extends Algebra(Monad, Comonad) { - #value - - /** - * @param {T} value - */ - constructor(value) { - super() - - this.#value = value - } - - /** - * @template T - * @param {T} value - */ - static of(value) { - return id(value) - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Identity} - */ - map(f) { - return id(f(this.#value)) - } - - /** - * @template U - * @type {Apply['ap']} - * @param {Apply>} b - * @returns {Apply} - */ - ap(b) { - return /** @type {Apply} */ (b.map(f => f(this.#value))) - } - - /** - * @template U - * @param {Morphism>} f - */ - chain(f) { - return f(this.#value) - } - - /** - * @param {(value: Identity) => T} f - */ - extend(f) { - return id(f(this)) - } - - extract() { - return this.#value - } - - toString() { - return `Identity(${this.#value})` - } -} - -/** - * @template T - * @param {T} value - */ -export const id = value => new Identity(value) - diff --git a/src/algebra/index.js b/src/algebra/index.js deleted file mode 100644 index 100f4ea..0000000 --- a/src/algebra/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export { TypeRef as Option, Some, None, some, none } from './option.js' -export { TypeRef as Result, Ok, Err, ok, err } from './result.js' -export { TypeRef as Free, liftF } from './free.js' -export { List, list } from './list.js' -export { IO } from './io.js' -export { Reader } from './reader.js' - diff --git a/src/algebra/interfaces.js b/src/algebra/interfaces.js deleted file mode 100644 index f877198..0000000 --- a/src/algebra/interfaces.js +++ /dev/null @@ -1,388 +0,0 @@ -import { mix, Mixin } 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} iterable - */ - constructor(iterable) { - this.#inner = new Map(iterable) - } - - /** - * @param {Iterable} keys - * @param {any} value - * @returns {this} - */ - set(keys, value) { - this.#inner.set(new Set(keys), value) - return this - } - - /** - * @param {Iterable} keys - * @returns {boolean} - */ - has(keys) { - return this.#inner.has(new Set(keys)) - } - - /** - * @param {Iterable} keys - */ - get(keys) { - return this.#inner.get(new Set(keys)) - } - - /** - * @param {Iterable} 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 Object.getPrototypeOf(target) - 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 - } -} - -const Implementations = Symbol() - -class Interface { - #name - - /** @type {MixinFunction} */ - #mixin - - /** @type {Set} */ - #methods = new Set() - - /** @type {Set} */ - #interfaces = new Set() - - /** @param {string} name */ - constructor(name) { - this.#name = name - this.#mixin = Mixin(this.build.bind(this)) - } - - get name() { - return this.#name - } - - findInterfaces(type) { - let interfaces = new Set() - let current = type - - while (current != null) { - interfaces = new Set(current[Implementations]).union(interfaces) - current = Object.getPrototypeOf(current) - } - - return interfaces - } - - /** - * @param {{ name: string }} type - * @returns {boolean} - */ - implementedBy(type) { - return this.findInterfaces(type).has(this) - } - - /** - * @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 {MixinFunction} - */ - asMixin() { - return this.#mixin - } - - /** - * @param {FunctionConstructor} base - */ - build(base) { - const interfaces = [...this.#interfaces.values()] - const mixins = interfaces.map(x => x.asMixin()) - - const Interfaces = mix(base).with(...mixins) - - const Algebra = class extends Interfaces { } - - for (const method of this.#methods) { - method.implement(this, Algebra) - } - - const prototype = Object.getPrototypeOf(Algebra) - prototype[Implementations] = new Set(interfaces) - - return Algebra - } -} - -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.asMixin())) -} - -/** - * @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') - diff --git a/src/algebra/io.js b/src/algebra/io.js deleted file mode 100644 index 47cfb7a..0000000 --- a/src/algebra/io.js +++ /dev/null @@ -1,63 +0,0 @@ -import { Algebra, Monad } from './interfaces.js' - -/** @import { Apply, Chain, Fn, Functor, Morphism } from './types.js' */ - -/** @template {Fn} T */ -export class IO extends Algebra(Monad) { - _effect - - /** - * @param {T} effect - */ - constructor(effect) { - super() - this._effect = effect - } - - /** - * @template {Fn} T - * @param {T} a - */ - static of(a) { - return new IO(() => a) - } - - /** - * @template {Fn} U - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {IO} - */ - chain(f) { - return /** @type {IO} */ (IO.of(() => f(this.run()).run())) - } - - /** - * @template {Fn} U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {IO} - */ - map(f) { - return /** @type {IO} */ (IO.of(() => f(this.run()))) - } - - /** - * @template {Fn} U - * @type {Apply['ap']} - * @param {IO>} other - * @returns {IO} - */ - ap(other) { - return /** @type {IO} */ (IO.of((() => other.run()(this.run())))) - } - - run() { - return this._effect() - } - - toString() { - return `IO(${this._effect})` - } -} - diff --git a/src/algebra/list.js b/src/algebra/list.js deleted file mode 100644 index 95dbda5..0000000 --- a/src/algebra/list.js +++ /dev/null @@ -1,252 +0,0 @@ -import { Algebra, Comonad, Foldable, Monoid, Semigroup } from './interfaces.js' - -/** @import { Apply, Chain, Foldable as FoldableT, Functor, Morphism, Semigroup as SemigroupT } from './types.js' */ - -const Interfaces = Algebra(Semigroup, Foldable, Monoid, Comonad) - -/** - * @template T - * @typedef {Element | Empty} List - */ - -/** @template T */ -class Empty extends Interfaces { - constructor() { - super() - } - - /** - * @template U - * @type {Chain['chain']} - * @this {Empty} - * @param {Morphism>} _f - * @returns {List} - */ - chain(_f) { - return /** @type {List} */ (this) - } - - /** - * @template U - * @type {Functor['map']} - * @this {Empty} - * @param {Morphism} _f - * @returns {List} - */ - map(_f) { - return this - } - - /** - * @template U - * @type {Apply['ap']} - * @this {Empty} - * @param {List>} _b - * @returns {List} - */ - ap(_b) { - return /** @type {List} */ (this) - } - - /** - * @type {SemigroupT['concat']} - */ - concat(b) { - return b - } - - /** - * @type {FoldableT['reduce']} - */ - reduce(_f, acc) { - return acc - } - - count() { - return 0 - } - - /** @returns {this is Empty} */ - isEmpty() { - return true - } - - toString() { - return `List(Empty)` - } -} - -/** @template T */ -class Element extends Interfaces { - #head - #tail - /** @type {List} */ - #cache - - /** - * @param {T} head - * @param {() => List} [tail] - */ - constructor(head, tail = List.empty) { - super() - this.#head = head - this.#tail = tail - this.#cache = null - } - /** - * @template U - * @type {Chain['chain']} - * @this {Element} - * @param {Morphism>} f - * @returns {List} - */ - chain(f) { - return /** @type {List} */ (f(this.#head).concat( - /** @type {never} */(this.tail().chain(f)) - )) - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {List} - */ - map(f) { - return new Element( - f(this.#head), - () => /** @type {List} */(this.tail().map(f)) - ) - } - - /** - * @template U - * @type {Apply['ap']} - * @this {Element} - * @param {List>} b - * @returns {List} - */ - ap(b) { - if (b.isEmpty()) { - return List.empty() - } - - const head = /** @type {List} */ (this.map(b.head())) - const rest = /** @type {List} */ (this.ap(b.tail())) - return /** @type {List} */ (head.concat(rest)) - } - - /** - * @type {SemigroupT['concat']} - */ - concat(b) { - return new Element( - this.#head, - () => /** @type {List} */(this.tail().concat(b)) - ) - } - - /** - * @type {FoldableT['reduce']} - */ - reduce(f, acc) { - return this.tail().reduce(f, f(acc, this.#head)) - } - - head() { - return this.#head - } - - tail() { - return this.#cache || (this.#cache = this.#tail()) - } - - count() { - return this.tail().count() + 1 - } - - /** @returns {this is Empty} */ - isEmpty() { - return false - } - - toArray() { - return this.reduce( - reduceArray, - [] - ) - } - - toString() { - return `List(${this.toArray()})` - } -} - -class TypeRef { - /** - * @template T - * @param {T} value - * @returns {List} - */ - static of(value) { - return new Element(value) - } - - /** - * @template T - * @param {Iterable} iterable - * @returns {List} - */ - static from(iterable) { - const iterator = Iterator.from(iterable) - return List.fromIterator(iterator) - } - - /** - * @template T - * @param {Iterator} iterator - * @returns {List} - */ - static fromIterator(iterator) { - const next = iterator.next() - - if (next.done) { - return List.empty() - } else { - return new Element(next.value, () => List.fromIterator(iterator)) - } - } - - /** - * @template T - * @param {T} head - * @param {List} tail - * @returns {List} - */ - static cons(head, tail) { - return new Element(head, () => tail) - } - - static empty() { - return empty - } -} - -/** - * @template T - * @param {T[]} acc - * @param {T} x - * @returns {T[]} - */ -const reduceArray = (acc, x) => acc.concat(x) - -export const List = TypeRef - -const empty = new Empty() - -/** - * @template T - * @param {T} value - */ -export const list = value => List.of(value) - diff --git a/src/algebra/option.js b/src/algebra/option.js deleted file mode 100644 index dafabf9..0000000 --- a/src/algebra/option.js +++ /dev/null @@ -1,174 +0,0 @@ -import { id, Identity } from './identity.js' -import { Algebra, Alternative, Foldable, Monad, Setoid } from './interfaces.js' - -/** @import { Alt, Apply, Chain, Foldable as FoldableT, Functor, Morphism, Setoid as SetoidT } from './types.js' */ - -const Interfaces = Algebra(Setoid, Alternative, Monad, Foldable) - -/** - * @template T - * @typedef {Some | None} Option - */ - -/** @template T */ -export class Some extends Interfaces { - /** @type {T} */ - #value - - /** @param {T} value */ - constructor(value) { - super() - - this.#value = value - } - - /** - * @type {SetoidT['equals']} - * @param {Some} other - */ - equals(other) { - if (other instanceof Some) { return false } - const eq = /** @type {Some} */ (other).chain(v => id(v === this.#value)) - return /** @type {Identity} */ (eq).extract() - } - - /** - * @template U - * @type {Apply['ap']} - * @param {Some>} b - * @returns {Some} - */ - ap(b) { - return /** @type {Some} */ (b.chain(f => - /** @type {Some} */(this.map(f)) - )) - } - - /** - * @type {Alt['alt']} - */ - alt(b) { - return this - } - - /** - * @type {Chain['chain']} - */ - chain(f) { - return f(this.#value) - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Some} - */ - map(f) { - return /** @type {Some} */ (this.chain(v => some(f(v)))) - } - - /** - * @type {Functor['map']} - */ - then(f) { - return this.map(f) - } - - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} f - * @param {U} init - * @returns {U} - */ - reduce(f, init) { - return f(init, this.#value) - } - - toString() { - return `Some(${this.#value})` - } -} - -/** @template T */ -export class None extends Interfaces { - /** - * @type {SetoidT['equals']} - */ - equals(other) { - return other === none - } - - /** - * @type {Apply['ap']} - * @returns {this} - */ - ap(_b) { - return this - } - - /** - * @type {Alt['alt']} - */ - alt(b) { - return b - } - - /** - * @template U - * @type {Chain['chain']} - * @param {Morphism} _f - * @returns {this} - */ - chain(_f) { - return this - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} _f - * @returns {this} - */ - map(_f) { - return this - } - - /** - * @template R - * @type {Functor['map']} - * @param {Morphism} _f - * @returns {None} - */ - then(_f) { - return this - } - - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} _f - * @param {U} init - * @returns {U} - */ - reduce(_f, init) { - return init - } - - toString() { - return 'None' - } -} - -/** - * @template T - * @param {T} value - */ -export const some = value => new Some(value) -export const none = new None() - -export const TypeRef = some -TypeRef.constructor['of'] = some -TypeRef.constructor['zero'] = none - diff --git a/src/algebra/reader.js b/src/algebra/reader.js deleted file mode 100644 index 39f5bf5..0000000 --- a/src/algebra/reader.js +++ /dev/null @@ -1,88 +0,0 @@ -import { id } from 'izuna' -import { Algebra, Monad } from './interfaces.js' - -/** @import { ApplicativeTypeRef, Apply, Chain, Functor, InferredMorphism, Morphism } from './types.js' */ - -/** - * @template T - * @typedef {InferredMorphism} ReaderFn - */ - -/** @template T */ -export class Reader extends Algebra(Monad) { - #run - - /** @param {InferredMorphism} run */ - constructor(run) { - super() - - this.#run = run - } - - /** - * @template T - * @type {ApplicativeTypeRef>['of']} - * @param {T} a - * @returns {Reader} - */ - static of(a) { - return new Reader(/** @type {InferredMorphism} */(_env => a)) - } - - /** @template T */ - static ask() { - return new Reader(/** @type {InferredMorphism} */(id)) - } - - /** - * @type {Functor['map']} - * @param {InferredMorphism} f - * @returns {Reader} - */ - map(f) { - return /** @type {Reader} */ (this.chain(value => Reader.of(f(value)))) - } - - /** - * @type {Chain['chain']} - * @param {InferredMorphism} f - * @returns {Reader} - */ - chain(f) { - return new Reader(env => { - const result = this.#run(env) - const next = f(result) - return next.run(env) - }) - } - - /** - * @template U - * @type {Apply['ap']} - * @param {Reader>} b - * @returns {Reader} - */ - ap(b) { - return /** @type {Reader} */ (b.chain(f => - /** @type {Reader} */(this.map(f)) - )) - } - - /** - * @param {InferredMorphism} f - * @returns {Reader} - */ - local(f) { - return new Reader(/** @type {InferredMorphism} */(env => this.#run(f(env)))) - } - - /** - * @template U - * @param {T} env - * @returns {U} - */ - run(env) { - return this.#run(env) - } -} - diff --git a/src/algebra/result.js b/src/algebra/result.js deleted file mode 100644 index 65f95a4..0000000 --- a/src/algebra/result.js +++ /dev/null @@ -1,287 +0,0 @@ -import { id, Identity } from './identity.js' -import { Algebra, Alternative, Bifunctor, Foldable, Monad, Setoid } from './interfaces.js' - -/** @import { Alt, Apply, Chain, Functor, Bifunctor as BifunctorT, Foldable as FoldableT, Morphism, Setoid as SetoidT } from './types.js' */ - -const Interfaces = Algebra(Setoid, Alternative, Monad, Foldable, Bifunctor) - -/** - * @template T, E - * @typedef {Ok | Err} Result - */ - -/** - * @template T, E - */ -export class Ok extends Interfaces { - /** @type {T} */ - #value - - /** - * @param {T} value - * @constructs {Ok} - */ - constructor(value) { - super() - - this.#value = value - } - - /** - * @type {SetoidT['equals']} - * @param {Result} other - * @returns {boolean} - */ - equals(other) { - if (!(other instanceof Ok)) { return false } - const eq = other.chain(v => (id(v === this.#value))) - return /** @type {Identity} */ (eq).extract() - } - - /** @returns {this is Ok} */ - isOk() { return true } - - /** @returns {this is Err} */ - isErr() { return false } - - /** - * @type {Chain['chain']} - */ - chain(f) { - return f(this.#value) - } - - /** - * @template E2 - * @type {Chain['chain']} - * @param {Morphism} _f - * @returns {this} - */ - chainErr(_f) { - return this - } - - /** - * @template U - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Functor} - */ - map(f) { - return this.chain(v => ok(f(v))) - } - - /** - * @template E2 - * @type {Functor['map']} - * @this {Result} - * @param {Morphism} _f - * @returns {Result} - */ - mapErr(_f) { - return /** @type {never} */ (this) - } - - /** - * @template U - * @type {Apply['ap']} - * @param {Apply>} b - * @returns {Result} - */ - ap(b) { - return /** @type {Result} */ (this.chain(v => - /** @type {Chain} */(b.map(f => f(v))) - )) - } - - /** - * @type Alt['alt'] - */ - alt(_b) { - return this - } - - /** - * @template U - * @borrows {Result~map} - * @param {Morphism} f - */ - then(f) { - return this.map(f) - } - - /** - * @template R - * @param {Morphism} _f - * @returns {this} - */ - catch(_f) { - return this - } - - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} f - * @param {U} init - * @returns {U} - */ - reduce(f, init) { - return f(init, this.#value) - } - - /** - * @template T2, E2 - * @type {BifunctorT['bimap']} - * @param {Morphism} f - * @param {Morphism} _g - * @returns {Result} - */ - bimap(f, _g) { - return /** @type {Result} */ (this.map(f)) - } - - toString() { - return `Ok(${this.#value})` - } -} - -/** - * @template T, E - */ -export class Err extends Interfaces { - /** @type {E} */ - #value - - /** - * @param {E} value - * @constructs {Err} - */ - constructor(value) { - super() - this.#value = value - } - - /** - * @type {SetoidT['equals']} - * @param {Err} other - * @returns {boolean} - */ - equals(other) { - if (!(other instanceof Err)) { return false } - const eq = other.chainErr(v => id(v === this.#value)) - return /** @type {Identity} */ (eq).extract() - } - - /** @returns {this is Ok} */ - isOk() { return false } - - /** @returns {this is Err} */ - isErr() { return true } - - /** - * @type {Chain['chain']} - * @returns {this} - */ - chain(_f) { - return this - } - - /** - * @template E2 - * @type {Chain['chain']} - * @param {Morphism>} f - * @returns {Result} - */ - chainErr(f) { - return f(this.#value) - } - - /** - * @type {Functor['map']} - * @returns {this} - */ - map(_f) { - return this - } - - /** - * @template E2 - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Result} - */ - mapErr(f) { - return /** @type {Result} */ (this.bimap(id, f)) - } - - /** - * @type {Functor['map']} - * @returns {this} - */ - then(_f) { - return this - } - - /** - * @template R - * @type {Functor['map']} - * @param {Morphism} f - * @returns {Err} - */ - catch(f) { - return new Err(f(this.#value)) - } - - /** - * @type Alt['alt'] - */ - alt(b) { - return b - } - - /** - * @template U - * @type {FoldableT['reduce']} - * @param {(acc: U, value: T) => U} _f - * @param {U} init - * @returns {U} - */ - reduce(_f, init) { - return init - } - - /** - * @template T2, E2 - * @type {BifunctorT['bimap']} - * @param {Morphism} _f - * @param {Morphism} g - * @returns {Result} - */ - bimap(_f, g) { - return /** @type {Result} */ (err(g(this.#value))) - } - - toString() { - return `Err(${this.#value})` - } -} - -/** - * @template T, E - * @param {T} v - * @returns {Ok} - */ -export const ok = v => new Ok(v) - -/** - * @template T, E - * @param {E} e - * @returns {Err} - */ -export const err = e => new Err(e) - -export const TypeRef = ok -TypeRef.constructor['of'] = ok -TypeRef.constructor['zero'] = err - diff --git a/src/algebra/types.js b/src/algebra/types.js deleted file mode 100644 index 550e1ad..0000000 --- a/src/algebra/types.js +++ /dev/null @@ -1,222 +0,0 @@ -export default {} - -/** - * @template T, R - * @typedef {(value: T) => R} Morphism - */ - -/** - * @template T - * @typedef {(value: T) => R} InferredMorphism - */ - -/** - * @template T - * @typedef {(value: T) => boolean} Predicate - */ - -/** - * @template T - * @typedef {{ - equals: (b: Setoid) => boolean - * }} Setoid - */ - -/** - * @template T - * @typedef {{ - lte: (b: Ord) => boolean - * }} Ord - */ - -/** - * @template T, U - * @typedef {*} Semigroupoid - */ - -/** - * @template I, J - * @typedef {{ - compose: (b: Semigroupoid) => Semigroupoid - * }} Category - */ - -/** - * @template T, U - * @template {Category} M - * @typedef {{ - id: () => (value: T) => M - * }} CategoryTypeRef - */ - -/** - * @template T - * @typedef {{ - concat: (b: Semigroup) => Semigroup - * }} Semigroup - */ - -/** - * @template T - * @typedef{Semigroup} Monoid - */ - -/** - * @template T - * @template {Monoid} M - * @typedef {{ - empty: () => M - * }} MonoidTypeRef - */ - -/** - * @template T - * @typedef { - Monoid & - { invert: () => Group } - * } Group - */ - -/** - * @template T - * @typedef {{ - filter: (f: (acc: U, val: T) => U, init: U) => U - * }} Filterable - */ - -/** - * @template T - * @typedef {{ - map: (f: Morphism) => Functor - * }} Functor - */ - -/** - * @template T - * @typedef {{ - contramap: (f: Morphism) => Contravariant - * }} Contravariant - */ - -/** - * @template T - * @typedef { - Functor & - { ap: (f: Apply>) => Apply } - * } Apply - */ - -/** - * @template T - * @typedef { - Functor & - Apply - * } Applicative - */ - -/** - * @template T - * @template {Applicative} M - * @typedef {{ - of: (value: T) => M - * }} ApplicativeTypeRef - */ - -/** - * @template T - * @typedef { - Functor & - { alt: (b: Alt) => Alt } - * } Alt - */ - -/** - * @template T - * @typedef {Alt} Plus - */ - -/** - * @template T - * @template {Plus} M - * @typedef { - Alt & - { zero: () => M } - * } PlusTypeRef - */ - -/** - * @template T - * @typedef {Applicative & Plus} Alternative - */ - -/** - * @template T - * @template {Applicative & Plus} M - * @typedef {ApplicativeTypeRef & PlusTypeRef} AlternativeTypeRef - */ - -/** - * @template T - * @typedef {{ - reduce: (f: (acc: U, val: T) => U, init: U) => U - * }} Foldable - */ - -/** - * @template T - * @typedef { - Functor & Foldable & - { traverse: (A: ApplicativeTypeRef>, f: (val: T) => Applicative) => Applicative> } - * } Traversable - */ - -/** - * @template T - * @typedef { - Apply & - { chain: (f: (value: T) => Chain) => Chain } - * } Chain - */ - -/** - * @template T - * @typedef {Chain} ChainRec - */ - -/** - * @template T - * @typedef {Functor & Applicative & Chain} Monad - */ - -/** - * @template T - * @template {Monad} M - * @typedef {ApplicativeTypeRef} MonadTypeDef - */ - -/** - * @template T - * @typedef { - Functor & - { extend: (f: (val: Extend) => U) => Extend} - * } Extend - */ - -/** - * @template T - * @typedef { - Extend & - { extract: () => T } - * } Comonad - */ - -/** - * @template A, X - * @typedef { - Functor & - { bimap: (f: Morphism, g: Morphism) => Bifunctor } - * } Bifunctor - */ - -/** @typedef {(...args: any[]) => any} Fn */ - diff --git a/src/algebra/utilities.js b/src/algebra/utilities.js deleted file mode 100644 index 6472900..0000000 --- a/src/algebra/utilities.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @template T, U - * @param {(acc: U, value: T) => U} f - * @param {U} acc - */ -export const Reducer = (f, acc) => { - function Const(value) { - this.value = value - } - - Const.of = function() { return new Const(acc) } - Const.prototype.map = function() { return this } - Const.prototype.ap = function(b) { - return new Const(f(b.value, this.value)) - } - return Const -} diff --git a/src/alias.js b/src/alias.js new file mode 100644 index 0000000..2a03942 --- /dev/null +++ b/src/alias.js @@ -0,0 +1,8 @@ +import { Method } from "./specification/index.js" + +export const alias = (type, aliases) => { + Object.entries(aliases).forEach(([method, impl]) => { + Method.from(impl).bind(type, method) + }) +} + diff --git a/src/common.js b/src/common.js new file mode 100644 index 0000000..de4c51e --- /dev/null +++ b/src/common.js @@ -0,0 +1,7 @@ +/** + * @template T + * @param {T} value + * @returns {T} + */ +export const id = value => value + diff --git a/src/error.js b/src/error.js new file mode 100644 index 0000000..1b4c938 --- /dev/null +++ b/src/error.js @@ -0,0 +1,8 @@ +export class UnwrapError extends Error { + name = 'UnwrapError' + + constructor(desc) { + super(`UnwrapError: ${desc}`) + } +} + diff --git a/src/index.js b/src/index.js index b1f26e0..e69de29 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +0,0 @@ -export * from './algebra/index.js' - diff --git a/src/mixin.js b/src/mixin.js deleted file mode 100644 index 31fe6ad..0000000 --- a/src/mixin.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * 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.} mixins - * @return {FunctionConstructor} a subclass of `superclass` with `mixins` applied - */ - with(...mixins) { - return mixins.reduce((c, m) => m(c), this.superclass) - } -} - diff --git a/src/option.js b/src/option.js new file mode 100644 index 0000000..90d7bbd --- /dev/null +++ b/src/option.js @@ -0,0 +1,450 @@ +import { id } from './common.js' +import { UnwrapError } from './error.js' +import { Result } from './result.js' +import { implementor } from './specification/index.js' +import { Alt, Applicative, Chain, Filterable, Functor, Ord, Setoid } from './specification/structures.js' +import { FantasyLand } from './specification/fantasy-land.js' + +export class Option { + /** + * @template T + * @param {T} value + * @returns {Some} + */ + static some(value) { + return new Some(value) + } + + /** @returns {None} */ + static none() { + return None + } + + /** + * @param {(value: T) => Option} fn + * @param {Option} self + * @returns {Option} + */ + static chain(fn, self) { + return self.bind(fn) + } + + /** + * @param {(value: T) => bool} fn + * @param {Option} self + * @returns {Option} + */ + static filter(predicate, self) { + return self.filter(predicate) + } + + /** + * @template V + * @param {(value: T) => V} fn + * @param {Option} self + * @returns {Option} + */ + static map(fn, self) { + return self.map(fn) + } + + /** + * @param {Option} other + * @param {Option} self + * @returns {Option} + */ + static alt(other, self) { + return self.alt(other) + } + + /** + * @param {Option} other + * @param {Option} self + * @returns {Option} + */ + static equals(other, self) { + return self.equals(other) + } + + /** + * @param {Option} other + * @param {Option} self + * @returns {Option} + */ + static lte(other, self) { + return self.lte(other) + } +} + +/** @template T */ +export class Some extends Option { + /** @type T */ + #value + + /** @param {T} value */ + constructor(value) { + super() + this.#value = value + } + + /** + * @returns {this is Some} + */ + isSome() { + return true + } + + /** + * @returns {this is _None} + */ + isNone() { + return false + } + + /** + * @param {(value: T) => Option} fn + * @returns {Option} + */ + bind(fn) { + return fn(this.#value) + } + + /** + * @template V + * @param {(value: T) => V} fn + * @returns {Option} + */ + map(fn) { + return new Some(fn(this.#value)) + } + + /** + * @param {Option} other + * @returns {Option} + */ + alt(_other) { + return this + } + + /** + * @param {Some} other + * @returns {Some} + */ + and(other) { + return other + } + + /** + * @param {() => Option} other + * @returns {Option} + */ + orElse(_fn) { + return this + } + + /** + * @template T + * @param {(value: T) => bool} predicate + * @returns {Option} + */ + filter(predicate) { + return predicate(this.#value) ? this : None + } + + /** + * @param {Option} other + * @returns {bool} + */ + equals(other) { + return other.isSome() && this.#value === other.#value + } + + /** + * @param {Option} other + * @returns {bool} + */ + lte(other) { + return other.isSome() && this.#value <= other.#value + } + + /** + * @template [V=T] + * @param {(acc: V, value: T) => V} reducer + * @param {V} init + * @returns {V} + */ + reduce(reducer, init) { + return reducer(init, this.#value) + } + + /** + * @returns {Option} + */ + flatten() { + if (this.#value instanceof Option) { + return this.bind(id) + } else { + return this + } + } + + /** + * @template E + * @param {E} _err + * @returns {Result} + */ + okOr(_err) { + return Result.ok(this.#value) + } + + /** + * @template E + * @template {() => E} F + * @param {F} _err + * @returns {Result} + */ + okOrElse(_err) { + return Result.ok(this.#value) + } + + /** + * @param {(value: T) => void} fn + * @returns {this} + */ + inspect(fn) { + fn(this.#value) + return this + } + + /** + * @returns {T} + * @throws {UnwrapError} + */ + unwrap() { + return this.#value + } + + /** + * @param {T} _value + * @returns {T} + */ + unwrapOr(_value) { + return this.unwrap() + } + + /** + * @param {() => T} _fn + * @returns {T} + */ + unwrapOrElse(_fn) { + return this.unwrap() + } + + /** @returns {string} */ + toString() { + return `Some(${this.#value})` + } +} + +class _None extends Option { + /** + * @returns {this is Some} + */ + isSome() { + return false + } + + /** + * @returns {this is _None} + */ + isNone() { + return true + } + + /** + * @template T + * @template {Option} R + * @param {(value: T) => R} fn + * @returns {R} + */ + bind(_fn) { + return this + } + + /** + * @template V + * @template {Option} R + * @param {(value: T) => V} fn + * @returns {R} + */ + map(_fn) { + return this + } + + /** + * @template T + * @param {Option} other + * @returns {Option} + */ + alt(other) { + return other + } + + /** + * @template T + * @param {() => Option} other + * @returns {Option} + */ + orElse(fn) { + return fn() + } + + /** + * @template T + * @param {Some} _other + * @returns {Some} + */ + and(_other) { + return this + } + + /** + * @template T + * @param {(value: T) => bool} _predicate + * @returns {Option} + */ + filter(_predicate) { + return this + } + + /** + * @template T + * @param {Option} other + * @returns {bool} + */ + equals(other) { + return other.isNone() + } + + /** + * @template T + * @param {Option} _other + * @returns {bool} + */ + lte(_other) { + return false + } + + /** + * @template T, [V=T] + * @param {(acc: V, value: T) => V} _reducer + * @param {V} init + * @returns {V} + */ + reduce(_reducer, init) { + return init + } + + /** + * @template T + * @template {Option} R + * @returns {R} + */ + flatten() { + return this + } + + /** + * @template T, E + * @template {Result} R + * @param {E} err + * @returns {R} + */ + okOr(err) { + return Result.err(err) + } + + /** + * @template T, E + * @template {Result} R + * @template {() => E} F + * @param {F} err + * @returns {R} + */ + okOrElse(err) { + return Result.err(err()) + } + + /** + * @template T + * @param {(value: T) => void} _fn + * @returns {this} + */ + inspect(_fn) { + return this + } + + /** + * @template T + * @returns {T} + * @throws {UnwrapError} + */ + unwrap() { + throw new UnwrapError('tried to unwrap None') + } + + /** + * @param {T} value + * @returns {T} + */ + unwrapOr(value) { + return value + } + + /** + * @template T + * @param {() => T} fn + * @returns {T} + */ + unwrapOrElse(fn) { + return fn() + } + + /** @returns {string} */ + toString() { + return 'None' + } +} + +export const None = new _None() + +implementor(Option) + .implements(Applicative) + .specifiedBy(FantasyLand) + .with({ of: Option.some }) + +const structures = [Setoid, Ord, Functor, Chain, Filterable, Alt] + +implementor(Some) + .implements(...structures) + .specifiedBy(FantasyLand) + .with({ + chain: Some.prototype.bind, + filter: Some.prototype.filter, + map: Some.prototype.map, + alt: Some.prototype.alt, + equals: Some.prototype.equals, + lte: Some.prototype.lte, + }) + +implementor(_None) + .implements(...structures) + .specifiedBy(FantasyLand) + .with({ + chain: _None.prototype.bind, + filter: _None.prototype.filter, + map: _None.prototype.map, + alt: _None.prototype.alt, + equals: _None.prototype.equals, + lte: _None.prototype.lte, + }) diff --git a/src/result.js b/src/result.js new file mode 100644 index 0000000..5f7b9ba --- /dev/null +++ b/src/result.js @@ -0,0 +1,426 @@ +import { id } from './common.js' +import { implementor } from './specification/index.js' +import { Alt, Applicative, Chain, Foldable, Functor, Ord, Setoid } from './specification/structures.js' +import { FantasyLand } from './specification/fantasy-land.js' +import { UnwrapError } from './error.js' +import { Option, None } from './option.js' + +export class Result { + /** + * @template T + * @param {T} value + */ + static ok(value) { + return new Ok(value) + } + + /** + * @template E + * @param {E} error + */ + static err(error) { + return new Err(error) + } + + // static-land methods + static chain(fn, self) { + return self.bind(fn) + } + + static map(fn, self) { + return self.map(fn) + } + + static alt(other, self) { + return self.alt(other) + } + + static equals(other, self) { + return self.equals(other) + } + + static lte(other, self) { + return self.lte(other) + } +} + +/** @template T */ +export class Ok extends Result { + /** @type T */ + #value + + /** @param {T} value */ + constructor(value) { + super() + this.#value = value + } + + /** + * @returns {bool} + */ + isOk() { + return true + } + + /** + * @returns {bool} + */ + isErr() { + return false + } + + /** + * @template E + * @template {Result} R + * @param {(value: T) => R} fn + * @returns {R} + */ + bind(fn) { + return fn(this.#value) + } + + /** + * @template V, E + * @template {Result} R + * @param {(value: T) => V} fn + * @returns {R} + */ + map(fn) { + return Result.ok(fn(this.#value)) + } + + /** + * @template E + * @param {Result} other + * @returns {Result} + */ + and(other) { + return other + } + + /** + * @template E + * @param {Result} other + * @returns {Result} + */ + alt(_other) { + return this + } + + /** + * @template E, F + * @param {(error: E) => Result} other + * @returns {Result} + */ + orElse(_fn) { + return this + } + + /** + * @template E + * @param {Result} other + * @returns {bool} + */ + equals(other) { + return other.isOk() && this.#value === other.#value + } + + /** + * @template E + * @param {Result} other + * @returns {bool} + */ + lte(other) { + return other.isOk() && this.#value <= other.#value + } + + /** + * @template [V=T] + * @param {(acc: V, value: T) => V} reducer + * @param {V} init + * @returns {V} + */ + reduce(reducer, init) { + return reducer(init, this.#value) + } + + /** + * @template E + * @template {Result} R + * @returns {R} + */ + flatten() { + if (this.#value instanceof Result) { + return this.bind(id) + } else { + return this + } + } + + /** + * @returns {Option} + */ + ok() { + return Option.some(this.#value) + } + + /** + * @returns {Option} + */ + err() { + return None + } + + /** + * @param {(value: T) => void} fn + * @returns {this} + */ + inspect(fn) { + fn(this.#value) + return this + } + + /** + * @returns {T} + * @throws {UnwrapError} + */ + unwrap() { + return this.#value + } + + /** + * @returns {E} + * @throws {UnwrapError} + */ + unwrapErr() { + throw new UnwrapError('tried to unwrapErr an Ok') + } + + /** + * @param {T} _value + * @returns {T} + */ + unwrapOr(_value) { + return this.unwrap() + } + + /** + * @param {() => T} _fn + * @returns {T} + */ + unwrapOrElse(_fn) { + return this.unwrap() + } + + /** @returns {string} */ + toString() { + return `Ok(${this.#value})` + } +} + +/** @template E */ +export class Err extends Result { + /** @type E */ + #error + + /** @param {E} value */ + constructor(error) { + super() + this.#error = error + } + + /** + * @returns {bool} + */ + isOk() { + return false + } + + /** + * @returns {bool} + */ + isErr() { + return true + } + + /** + * @template T + * @template {Result} R + * @param {(value: T) => R} fn + * @returns {R} + */ + bind(_fn) { + return this + } + + /** + * @template V, E + * @template {Result} R + * @param {(value: T) => V} fn + * @returns {R} + */ + map(_fn) { + return this + } + + /** + * @template T + * @param {Result} _other + * @returns {Result} + */ + and(_other) { + return this + } + + /** + * @template T + * @param {Result} other + * @returns {Result} + */ + alt(other) { + return other + } + + /** + * @template T, F + * @param {(error: E) => Result} other + * @returns {Result} + */ + orElse(fn) { + return fn(this.#error) + } + + /** + * @template T + * @param {Result} other + * @returns {bool} + */ + equals(other) { + return other.isErr() && this.#error === other.#error + } + + /** + * @template T + * @param {Result} other + * @returns {bool} + */ + lte(other) { + return other.isErr() && this.#error <= other.#error + } + + /** + * @template [V=T] + * @param {(acc: V, value: T) => V} _reducer + * @param {V} init + * @returns {V} + */ + reduce(_reducer, init) { + return init + } + + /** + * @template T + * @template {Result} R + * @returns {R} + */ + flatten() { + if (this.#error instanceof Err) { + return this.bind(id) + } else { + return this + } + } + + /** + * @returns {Option} + */ + ok() { + return None + } + + /** + * @returns {Option} + */ + err() { + return Option.some(this.#error) + } + + /** + * @template T + * @param {(value: T) => void} _fn + * @returns {this} + */ + inspect(_fn) { + return this + } + + /** + * @template T + * @returns {T} + * @throws {UnwrapError} + */ + unwrap() { + throw new UnwrapError('tried to unwrap an Err') + } + + /** + * @returns {E} + * @throws {UnwrapError} + */ + unwrapErr() { + return this.#error + } + + /** + * @param {T} value + * @returns {T} + */ + unwrapOr(value) { + return value + } + + /** + * @template T + * @param {() => T} fn + * @returns {T} + */ + unwrapOrElse(fn) { + return fn() + } + + /** @returns {string} */ + toString() { + return `Err(${this.#error})` + } +} + +implementor(Result) + .implements(Applicative) + .specifiedBy(FantasyLand) + .with({ of: Result.ok }) + +const structures = [Setoid, Ord, Functor, Chain, Alt, Foldable] + +implementor(Ok) + .implements(...structures) + .specifiedBy(FantasyLand) + .with({ + chain: Ok.prototype.bind, + map: Ok.prototype.map, + reduce: Ok.prototype.reduce, + alt: Ok.prototype.alt, + equals: Ok.prototype.equals, + lte: Ok.prototype.lte, + }) + +implementor(Err) + .implements(...structures) + .specifiedBy(FantasyLand) + .with({ + chain: Err.prototype.bind, + map: Err.prototype.map, + reduce: Err.prototype.reduce, + alt: Err.prototype.alt, + equals: Err.prototype.equals, + lte: Err.prototype.lte, + }) diff --git a/src/specification/fantasy-land.js b/src/specification/fantasy-land.js new file mode 100644 index 0000000..fa752e9 --- /dev/null +++ b/src/specification/fantasy-land.js @@ -0,0 +1,19 @@ +import { zip } from '../utils.js' +import { Specification } from './index.js' +import * as Structures from './structures.js' + +const prefix = name => `fantasy-land/${name}` + +const structures = Object.values(Structures).map(struct => { + const methods = struct.methods.map(m => m.name) + if (methods.length === 0) { + return struct + } + + const overrides = methods.map(prefix) + const schema = Object.fromEntries(zip(methods, overrides)) + return struct.with(schema) +}) + +export const FantasyLand = new Specification('fantasy-land', structures) + diff --git a/src/specification/index.js b/src/specification/index.js new file mode 100644 index 0000000..6fe6a23 --- /dev/null +++ b/src/specification/index.js @@ -0,0 +1,293 @@ +import { path } from '../utils.js' + +export class Method { + name + method + isStatic + + constructor(name, method = name, { isStatic = false } = {}) { + this.name = name + this.method = method + this.isStatic = isStatic + } + + static from(value, options) { + if (value instanceof Method) { + return value + } else { + return new Method(value, options) + } + } + + static asStatic(value, options) { + return new Method(value, { isStatic: true, ...options }) + } + + #getBindTarget(target) { + if (this.isStatic) { + return target + } else { + return target.prototype + } + } + + bind(target, impl) { + this.#getBindTarget(target)[this.method] = impl + } + + clone() { + return new Method(this.name, this.method, { isStatic: this.isStatic }) + } +} + +export class Structure { + name + methods + dependencies + #methodsMap + + constructor(name, methods = [], dependencies = []) { + this.name = name + this.dependencies = dependencies + + this.methods = [] + this.#methodsMap = new Map() + methods.forEach(m => this.hasMethod(m)) + } + + hasMethod(method, options) { + if (!(method instanceof Method)) { + method = Method.from(method, options) + } + + + this.methods.push(method) + this.#methodsMap.set(method.name, method) + return this + } + + dependsOn(...algebras) { + this.dependencies.push(...algebras) + return this + } + + getMethod(name) { + return this.#methodsMap.get(name) + } + + clone() { + return new Structure( + this.name, + this.methods.map(m => m.clone()), + this.dependencies.slice()) + } + + with(aliases) { + const clonedStructure = this.clone() + + if (!(aliases instanceof AliasMap)) { + aliases = AliasMap.from(aliases) + } + + for (const [id, override] of aliases.schema.entries()) { + const method = clonedStructure.getMethod(id) + if (method) { + method.method = override + } else { + console.warn(`Warning: Attempted to override method '${id}' not found in Structure '${this.name}'.`) + } + } + + return clonedStructure + } +} + +class DependencyGraph { + #structures + #methods + + constructor(structures) { + this.#structures = new Map() + + for (const struct of structures) { + this.#structures.set(struct.name, struct) + } + + this.#methods = new Map() + this.#build() + } + + #build() { + const visited = new Set() + const stack = [] + + for (const structure of this.#structures.values()) { + stack.push(structure) + } + + while (stack.length > 0) { + const current = stack.pop() + + if (visited.has(current.name)) { + continue + } + visited.add(current.name) + + for (const method of current.methods) { + if (!this.#methods.has(method.name)) { + this.#methods.set(method.name, method) + } + } + + for (const dep of current.dependencies) { + const resolvedDep = this.#structures.get(dep.name) || dep + stack.push(resolvedDep) + } + } + } + + getMethods() { + return this.#methods + } +} + +export class Specification { + name + structures + #dependencies + + constructor(name, structures) { + this.name = name + + if (structures instanceof Map) { + this.structures = structures + } else { + this.structures = new Map() + + for (const struct of structures) { + if (!(struct instanceof Structure)) { + throw new TypeError("Expected an array of Structure instances.") + } + this.structures.set(struct.name, struct) + } + } + + this.#dependencies = new DependencyGraph(this.structures.values()) + } + + implementedBy(target) { + return new Implementor(target, this) + } + + getOverrides(structs) { + return structs.map(s => this.structures.get(s.name)) + } + + getAllMethods() { + return this.#dependencies.getMethods() + } +} + +export class Implementor { + target + structures = [] + specification + + constructor(target, specification = undefined) { + this.target = target + this.specification = specification + } + + implements(...structures) { + for (const struct of structures) { + let structure + + if (struct instanceof Structure) { + structure = struct + } else if (typeof struct === 'string') { + structure = this.specification.getStructure(struct) + if (!structure) { + console.warn(`Warning: Structure '${struct}' not found in specification '${this.specification.name}'.`) + continue + } + } else { + throw new TypeError(`Expected Structure instance or string name, got ${typeof struct}.`) + } + + if (structure) { + this.structures.push(structure) + } + } + + return this + } + + specifiedBy(specification) { + this.specification = specification + return this + } + + with(aliases) { + if (!(aliases instanceof AliasMap)) { + aliases = AliasMap.from(aliases) + } + + let methods + if (this.structures.size === 0) { + console.warn(`${this.target.name} is implementing ${this.specification.name} with no specified structures`) + methods = this.specification.getMethods() + } else { + const overrides = this.specification.getOverrides(this.structures) + methods = new DependencyGraph(overrides).getMethods() + } + + for (const [alias, impl] of aliases.schema.entries()) { + const method = methods.get(alias) + + if (method) { + let bindMethod + + if (typeof impl === 'function') { + bindMethod = impl + } else if (typeof impl === 'string') { + bindMethod = path(impl, this.target) + if (bindMethod == null || typeof bindMethod !== 'function') { + throw new TypeError(`Could not resolve string alias '${impl}' for specification method '${alias}'`) + } + } else { + throw new TypeError(`Alias '${alias}' has an invalid implementation value. Expected function or string.`) + } + + if (bindMethod != null) { + method.bind(this.target, bindMethod) + } else { + console.warn(`Warning: No valid implementation found for alias '${alias}'.`) + } + } else { + console.warn(`Warning: Alias '${alias}' does not correspond to any method in the specification.`) + } + } + } +} + +export class AliasMap { + schema + + constructor(schema = new Map()) { + this.schema = schema + } + + static from(iterable) { + if (iterable instanceof Map) { + return new AliasMap(iterable) + } else if (Array.isArray(iterable) || typeof iterable[Symbol.iterator] == 'function') { + return new AliasMap(new Map(iterable)) + } else if (typeof iterable === 'object') { + return new AliasMap(new Map(Object.entries(iterable))) + } else { + throw new TypeError("can't make bind target from given schema") + } + } +} + +export const implementor = (target, specification = null) => new Implementor(target, specification) diff --git a/src/specification/static-land.js b/src/specification/static-land.js new file mode 100644 index 0000000..e69de29 diff --git a/src/specification/structures.js b/src/specification/structures.js new file mode 100644 index 0000000..0f2b971 --- /dev/null +++ b/src/specification/structures.js @@ -0,0 +1,91 @@ +import { Method, Structure } from './index.js' + +const method = (name, isStatic = false) => new Method(name, name, { isStatic }) + +export const Setoid = new Structure('Setoid') + .hasMethod(method('equals')) + +export const Ord = new Structure('Ord') + .hasMethod(method('lte')) + .dependsOn(Setoid) + +export const Semigroupoid = new Structure('Semigroupoid') + .hasMethod(method('compose')) + +export const Category = new Structure('Category') + .hasMethod(method('id', true)) + .dependsOn(Semigroupoid) + +export const Semigroup = new Structure('Semigroup') + .hasMethod(method('concat')) + +export const Monoid = new Structure('Monoid') + .hasMethod(method('empty', true)) + .dependsOn(Semigroup) + +export const Group = new Structure('Group') + .hasMethod(method('invert')) + .dependsOn(Monoid) + +export const Foldable = new Structure('Foldable') + .hasMethod(method('reduce')) + +export const Functor = new Structure('Functor') + .hasMethod(method('map')) + +export const Traversable = new Structure('Traversable') + .hasMethod(method('traverse')) + .dependsOn(Foldable, Functor) + +export const Profunctor = new Structure('Profunctor') + .hasMethod(method('promap')) + .dependsOn(Functor) + +export const Alt = new Structure('Alt') + .hasMethod(method('alt')) + .dependsOn(Functor) + +export const Plus = new Structure('Plus') + .hasMethod(method('zero', true)) + .dependsOn(Alt) + +export const Apply = new Structure('Apply') + .hasMethod(method('ap')) + .dependsOn(Functor) + +export const Applicative = new Structure('Applicative') + .hasMethod(method('of', true)) + .dependsOn(Apply) + +export const Chain = new Structure('Chain') + .hasMethod(method('chain')) + .dependsOn(Apply) + +export const ChainRec = new Structure('ChainRec') + .hasMethod(method('chainRec')) + .dependsOn(Chain) + +export const Alternative = new Structure('Alternative') + .dependsOn(Plus, Applicative) + +export const Monad = new Structure('Monad') + .dependsOn(Applicative, Chain) + +export const Bifunctor = new Structure('Bifunctor') + .hasMethod(method('bimap')) + .dependsOn(Functor) + +export const Extend = new Structure('Extend') + .hasMethod(method('extend')) + .dependsOn(Functor) + +export const Comonad = new Structure('Comonad') + .hasMethod(method('extract')) + .dependsOn(Extend) + +export const Contravariant = new Structure('Contravariant') + .hasMethod(method('contramap')) + +export const Filterable = new Structure('Filterable') + .hasMethod(method('filter')) + diff --git a/src/union.js b/src/union.js deleted file mode 100644 index 4c95d0b..0000000 --- a/src/union.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @template R - * @typedef {(...args: any[]) => R} Fn - */ - -/** - * @template R - * @typedef {() => R} EmptyFn - */ - -/** - * @template R - * @callback cata - * @param {Record> & Partial>>} pattern - * @returns {R} - * - * @throws MatchError - */ - -/** - * @typedef Variant - * @property {cata} cata - */ - -/** - * @typedef {(...values: any[]) => Variant} VariantConstructor - */ - -/** - * @template {PropertyKey[]} Variant - * @typedef {{ [key in Variant[number]]: (...values: any) => Variant }} Variants - */ - -/** - * @template {PropertyKey} T - * @template {PropertyKey[]} U - * @typedef {{ is: typeof is } & Variants} Union - */ - -const Tag = Symbol('Tag') - -export class CatamorphismError extends Error { - /** @param {PropertyKey} name */ - constructor(name) { - super(`unmatched arm in catamorphism: ${name.toString()}`) - } -} - -/** - * @param {PropertyKey} type - * @param {PropertyKey} variant - * @returns {(...values: any[]) => Variant} - */ -const Variant = (type, variant) => (...values) => ({ - [Tag]: type, - cata(pattern) { - if (variant in pattern) { - // NOTE: this is a workaround for typescript not recognizing - // that objects can be indexed with symbols - return pattern[ /** @type {string} */ (variant)](...values) - } else if ('_' in pattern) { - return pattern._() - } else { - throw new CatamorphismError(variant) - } - } -}) - -/** - * @template T, U - * @this {Union} - * @param {any} other - * @returns {other is Variant} - */ -function is(other) { - return Object.hasOwn(other, Tag) && other[Tag] === this[Tag] -} - -/** - * @template {string} const T - * @template {Array} const U - * @param {T} typeName - * @param {...U} variantNames - * @returns {Union} - */ -export const Union = (typeName, variantNames) => { - const typeTag = Symbol(typeName) - const tag = { [Tag]: typeTag, is } - const variants = Object.fromEntries(variantNames.map(v => [v, Variant(typeTag, v)])) - const result = Object.assign(tag, variants) - - - return /** @type {Union} */ (result) -} - diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..9e79691 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,22 @@ +export const path = (path, obj) => { + const parts = path.split('.') + let current = obj + let found = true + for (let i = 0; i < parts.length; i++) { + if (current && typeof current === 'object' && parts[i] in current) { + current = current[parts[i]] + } else { + found = false + break + } + } + + return found +} + +export const zip = (...xs) => { + const maxLength = Math.max(...xs.map(arr => arr.length)) + return Array.from({ length: maxLength }).map((_, i) => + xs.map(arr => arr[i]) + ) +} diff --git a/tests/index.js b/tests/index.js deleted file mode 100755 index d9b03db..0000000 --- a/tests/index.js +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node - -import { TerminalRunner } from 'folktest' -import * as Tests from './units/index.js' - -console.log(TerminalRunner(Tests).toString()) - diff --git a/tests/option.test.js b/tests/option.test.js new file mode 100644 index 0000000..19898c6 --- /dev/null +++ b/tests/option.test.js @@ -0,0 +1,228 @@ +import { Option, Some, None } from '../src/option.js' +import { UnwrapError } from '../src/error.js' +import { Result } from '../src/result.js' +import { describe, it, mock } from 'node:test' +import assert from 'node:assert/strict' + +const some = v => new Some(v) +const eq = assert.deepStrictEqual.bind(assert) + +describe('Option', () => { + it('Option.some should return a Some', async () => { + eq(Option.some(1), new Some(1)) + + // fantasy-land/of + eq(Option['fantasy-land/of'](1), new Some(1)) + }) + + it('Some.bind(fn) should call fn', async () => { + const a = some(1) + let res = a.bind(x => some(x * 2)) + eq(res, some(2)) + + // fantasy-land/chain + res = a['fantasy-land/chain'](x => some(x * 2)) + eq(res, some(2)) + }) + + it('None.bind(fn) should not execute fn', async () => { + const fn = mock.fn(v => some(v * 2)) + let res = None.bind(fn) + eq(res, None) + eq(fn.mock.callCount(), 0) + + // fantasy-land/chain + res = None['fantasy-land/chain'](fn) + eq(res, None) + eq(fn.mock.callCount(), 0) + }) + + it('Some.map(fn) should transform the inner value', () => { + const a = some(1) + let res = a.map(x => x + 1) + eq(res, some(2)) + + // fantasy-land/map + res = a['fantasy-land/map'](x => x + 1) + eq(res, some(2)) + }) + + it('None.map(fn) should not execute fn', async () => { + const fn = mock.fn(v => some(v * 2)) + let res = None.map(fn) + eq(res, None) + eq(fn.mock.callCount(), 0) + + // fantasy-land/chain + res = None['fantasy-land/map'](fn) + eq(res, None) + eq(fn.mock.callCount(), 0) + }) + + it('Some.and(other)', () => { + const a = some(1) + const b = some(2) + assert.strictEqual(a.and(b), b) + }) + + it('None.and(other)', () => { + assert.strictEqual(None.and(some(1)), None) + }) + + it('Some.alt(other) should return itself', () => { + const self = some(1) + const other = some(2) + let res = self.alt(other) + assert.strictEqual(res, self) + + // fantasy-land/alt + res = self['fantasy-land/alt'](other) + assert.strictEqual(res, self) + }) + + it('None.alt(other) should return other', () => { + const other = some(2) + let res = None.alt(other) + assert.strictEqual(res, other) + + // fantasy-land/alt + res = None['fantasy-land/alt'](other) + assert.strictEqual(res, other) + }) + + it('Option.orElse()', () => { + const nothing = mock.fn(() => None) + const chocolate = mock.fn(() => some('chocolate')) + eq(some('vanilla').orElse(chocolate), some('vanilla')) + eq(chocolate.mock.callCount(), 0) + eq(None.orElse(chocolate), some('chocolate')) + eq(chocolate.mock.callCount(), 1) + eq(None.orElse(nothing), None) + eq(nothing.mock.callCount(), 1) + }) + + it('Some.filter(predicate)', () => { + const a = some(1) + assert.strictEqual(a.filter(x => x == 1), a) + assert.strictEqual(a.filter(x => x == 2), None) + + // fantasy-land/filter + assert.strictEqual(a['fantasy-land/filter'](x => x == 1), a) + }) + + it('None.filter(predicate)', () => { + assert.strictEqual(None.filter(x => x == 1), None) + + // fantasy-land/filter + assert.strictEqual(None['fantasy-land/filter'](x => x == 1), None) + }) + + it('Some.okOr(err)', () => { + eq(some(1).okOr(2), Result.ok(1)) + }) + + it('None.okOr(err)', () => { + eq(None.okOr(2), Result.err(2)) + }) + + it('Some.okOrElse(err)', () => { + eq(some(1).okOrElse(() => 2), Result.ok(1)) + }) + + it('None.okOr(err)', () => { + eq(None.okOrElse(() => 2), Result.err(2)) + }) + + it('Some.equals(other)', () => { + const a = some(1) + + assert.ok(a.equals(some(1))) + assert(!a.equals(some(2))) + assert(!a.equals(None)) + + // fantasy-land/equals + assert.ok(a['fantasy-land/equals'](some(1))) + }) + + it('None.equals(other)', () => { + assert.ok(None.equals(None)) + assert(!None.equals(some(1))) + + // fantasy-land/equals + assert.ok(None['fantasy-land/equals'](None)) + }) + + it('Some.lte(other)', () => { + const a = some(1) + + assert.ok(a.lte(some(1))) + assert.ok(a.lte(some(2))) + assert.ok(!a.lte(some(0))) + assert(!a.lte(None)) + + // fantasy-land/lte + assert.ok(a['fantasy-land/lte'](some(1))) + }) + + it('None.lte(other)', () => { + assert(!None.lte(None)) + assert(!None.lte(some(1))) + + // fantasy-land/lte + assert.ok(!None['fantasy-land/lte'](None)) + }) + + it('Some.flatten()', async () => { + eq(some(1).flatten(), some(1)) + eq(some(some(1)).flatten(), some(1)) + eq(some(some(some(1))).flatten(), some(some(1))) + eq(some(None).flatten(), None) + }) + + it('None.flatten()', async () => { + eq(None.flatten(), None) + }) + + it('Some.inspect(fn) should call fn', async () => { + const fn = mock.fn(_x => { }) + some(1).inspect(fn) + eq(fn.mock.callCount(), 1) + }) + + it('None.inspect(fn) should not call fn', async () => { + const fn = mock.fn(_x => { }) + None.inspect(fn) + eq(fn.mock.callCount(), 0) + }) + + it('Some.unwrap() returns inner value', async () => { + const a = some(1) + assert.doesNotThrow(a.unwrap.bind(a), UnwrapError) + eq(a.unwrap(), 1) + }) + + it('None.unwrap() throws UnwrapError', async () => { + assert.throws(None.unwrap, UnwrapError) + }) + + it('Some.unwrapOr(value) returns inner value', async () => { + eq(some(1).unwrapOr(2), 1) + }) + + it('None.unwrapOr(value) returns value', async () => { + eq(None.unwrapOr(2), 2) + }) + + it('Some.unwrapOrElse(fn) returns inner value', async () => { + const fn = mock.fn(() => 2) + const a = some(1) + eq(a.unwrapOrElse(fn), 1) + eq(fn.mock.callCount(), 0) + }) + + it('None.unwrapOrElse(fn) returns result of fn', async () => { + const fn = mock.fn(() => 2) + eq(None.unwrapOrElse(fn), 2) + eq(fn.mock.callCount(), 1) + }) +}) diff --git a/tests/result.test.js b/tests/result.test.js new file mode 100644 index 0000000..9984f8c --- /dev/null +++ b/tests/result.test.js @@ -0,0 +1,276 @@ +import { Result, Ok, Err } from '../src/result.js' +import { UnwrapError } from '../src/error.js' +import { Option } from '../src/option.js' +import { describe, it, mock } from 'node:test' +import assert from 'node:assert/strict' + +const ok = v => new Ok(v) +const err = e => new Err(e) +const eq = assert.deepStrictEqual.bind(assert) + +describe('Result', () => { + it('Result.ok should return an Ok value', async () => { + eq(Result.ok(1), new Ok(1)) + + // fantasy-land/of + eq(Result['fantasy-land/of'](1), new Ok(1)) + }) + + it('Ok.bind(fn) should call fn', async () => { + const a = ok(1) + const fn = x => ok(x * 2) + let res = a.bind(fn) + eq(res, ok(2)) + + // fantasy-land/chain + res = a['fantasy-land/chain'](fn) + eq(res, ok(2)) + + // static-land chain + res = Result.chain(fn, a) + eq(res, ok(2)) + }) + + it('Err.bind(fn) should not execute fn', async () => { + const fn = mock.fn(v => ok(v * 2)) + const e = err(new Error('failed')) + let res = e.bind(fn) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + + // fantasy-land/chain + res = e['fantasy-land/chain'](fn) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + + // static-land chain + res = Result.chain(fn, e) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + }) + + it('Ok.map(fn) should transform the inner value', () => { + const a = ok(1) + const fn = x => x + 1 + let res = a.map(fn) + eq(res, ok(2)) + + // fantasy-land/map + res = a['fantasy-land/map'](fn) + eq(res, ok(2)) + + // static-land map + res = Result.map(fn, a) + eq(res, ok(2)) + }) + + it('Err.map(fn) should not execute fn', async () => { + const fn = mock.fn(v => Result.ok(v * 2)) + const e = err(new Error('failed')) + let res = e.map(fn) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + + // fantasy-land/chain + res = e['fantasy-land/map'](fn) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + + // static-land chain + res = Result.map(fn, e) + eq(res, err(new Error('failed'))) + eq(fn.mock.callCount(), 0) + }) + + it('Ok.and(other)', () => { + const a = ok(1) + const b = ok(2) + assert.strictEqual(a.and(b), b) + }) + + it('Err.and(other)', () => { + const a = err(1) + const b = ok(2) + assert.strictEqual(a.and(b), a) + }) + + it('Ok.alt(other) should return itself', () => { + const self = ok(1) + const other = ok(2) + let res = self.alt(other) + assert.strictEqual(res, self) + + // fantasy-land/alt + res = self['fantasy-land/alt'](other) + assert.strictEqual(res, self) + + // static-land alt + res = Result.alt(other, self) + assert.strictEqual(res, self) + }) + + it('Err.alt(other) should return other', () => { + const self = err(new Error('failure')) + const other = ok(2) + let res = self.alt(other) + assert.strictEqual(res, other) + + // fantasy-land/alt + res = self['fantasy-land/alt'](other) + assert.strictEqual(res, other) + + // static-land alt + res = Result.alt(other, self) + assert.strictEqual(res, other) + }) + + it('Result.orElse(fn)', () => { + const sq = x => ok(x * x) + eq(ok(2).orElse(sq).orElse(sq), ok(2)) + eq(ok(2).orElse(err).orElse(sq), ok(2)) + eq(err(3).orElse(sq).orElse(err), ok(9)) + eq(err(3).orElse(err).orElse(err), err(3)) + }) + + it('Ok.ok()', () => { + eq(ok(1).ok(), Option.some(1)) + }) + + it('Err.ok()', () => { + eq(err(1).ok(), Option.none()) + }) + + it('Ok.err()', () => { + eq(ok(1).err(), Option.none()) + }) + + it('Err.err()', () => { + eq(err(1).err(), Option.some(1)) + }) + + it('Ok.equals(other)', () => { + const a = ok(1) + + assert.ok(a.equals(ok(1))) + assert(!a.equals(ok(2))) + assert(!a.equals(err(1))) + + // fantasy-land/equals + assert.ok(a['fantasy-land/equals'](ok(1))) + + // static-land equals + assert.ok(Result.equals(ok(1), a)) + }) + + it('Err.equals(other)', () => { + const a = err(1) + + assert.ok(a.equals(err(1))) + assert(!a.equals(err(2))) + assert(!a.equals(ok(1))) + + // fantasy-land/equals + assert.ok(a['fantasy-land/equals'](err(1))) + + // static-land equals + assert.ok(Result.equals(err(1), a)) + }) + + it('Ok.lte(other)', () => { + const a = ok(1) + + assert.ok(a.lte(ok(1))) + assert.ok(a.lte(ok(2))) + assert.ok(!a.lte(ok(0))) + assert(!a.lte(err(1))) + + // fantasy-land/lte + assert.ok(a['fantasy-land/lte'](ok(1))) + + // static-land lte + assert.ok(Result.lte(ok(1), a)) + }) + + it('Err.lte(other)', () => { + const a = err(1) + + assert.ok(a.lte(err(1))) + assert.ok(a.lte(err(2))) + assert.ok(!a.lte(err(0))) + assert(!a.lte(ok(1))) + + // fantasy-land/lte + assert.ok(a['fantasy-land/lte'](err(1))) + + // static-land lte + assert.ok(Result.lte(err(1), a)) + }) + + it('Ok.flatten()', async () => { + eq(ok(1).flatten(), ok(1)) + eq(ok(ok(1)).flatten(), ok(1)) + eq(ok(ok(ok(1))).flatten(), ok(ok(1))) + eq(ok(err(1)).flatten(), err(1)) + }) + + it('Err.flatten()', async () => { + eq(err(1).flatten(), err(1)) + eq(err(err(1)).flatten(), err(1)) + }) + + it('Ok.inspect(fn) should call fn', async () => { + const fn = mock.fn(_x => { }) + ok(1).inspect(fn) + eq(fn.mock.callCount(), 1) + }) + + it('Err.inspect(fn) should not call fn', async () => { + const fn = mock.fn(_x => { }) + err(1).inspect(fn) + eq(fn.mock.callCount(), 0) + }) + + it('Ok.unwrap() returns inner value', async () => { + const a = ok(1) + assert.doesNotThrow(a.unwrap.bind(a), UnwrapError) + eq(a.unwrap(), 1) + }) + + it('Err.unwrap() throws UnwrapError', async () => { + assert.throws(err(1).unwrap, UnwrapError) + }) + + it('Ok.unwrapErr() throws UnwrapError', async () => { + const a = ok(1) + assert.throws(a.unwrapErr, UnwrapError) + }) + + it('Err.unwrapErr() returns inner error', async () => { + const a = err(1) + assert.doesNotThrow(a.unwrapErr.bind(a), UnwrapError) + eq(a.unwrapErr(), 1) + }) + + it('Ok.unwrapOr(value) returns inner value', async () => { + eq(ok(1).unwrapOr(2), 1) + }) + + it('Err.unwrapOr(value) returns value', async () => { + eq(err(1).unwrapOr(2), 2) + }) + + it('Ok.unwrapOrElse(fn) returns inner value', async () => { + const fn = mock.fn(() => 2) + const a = ok(1) + eq(a.unwrapOrElse(fn), 1) + eq(fn.mock.callCount(), 0) + }) + + it('Err.unwrapOrElse(fn) returns result of fn', async () => { + const fn = mock.fn(() => 2) + const a = err(1) + eq(a.unwrapOrElse(fn), 2) + eq(fn.mock.callCount(), 1) + }) +}) + diff --git a/tests/units/index.js b/tests/units/index.js deleted file mode 100644 index 8e7f032..0000000 --- a/tests/units/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { Tests as Monad } from './monad.js' - diff --git a/tests/units/monad.js b/tests/units/monad.js deleted file mode 100644 index c335d5f..0000000 --- a/tests/units/monad.js +++ /dev/null @@ -1,48 +0,0 @@ -// @ts-nocheck -import { it, assert } from 'folktest' -import { IO, List, Option, Reader, Result, Some } from '../../src/index.js' -import { Applicative, Chain, Ord, Setoid } from '../../src/algebra/interfaces.js' - -const Algebra = [Option, Result, List, IO, Reader] - -const inc = F => x => F.of(x + 1) -const dbl = F => x => F.of(x * 2) - -const impl = i => m => i.implementedBy(m) - -export const Tests = [ - it('unit is a left identity', () => { - Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => { - const f = inc(algebra) - - assert( - algebra.of(1).chain(f).equals(f(1)), - `${algebra.name} is not a left identity` - ) - }) - }), - - it('unit is a right identity', () => { - Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => { - const m = algebra.of(1) - - assert( - m.chain(algebra.of).equals(m), - `${algebra.name} is not a right identity` - ) - }) - }), - - it('unit is associative', () => { - Algebra.filter(impl(Setoid, Applicative, Chain)).forEach(algebra => { - const a = algebra.of(1) - const f = inc(algebra) - const g = dbl(algebra) - - const x = a.chain(f).chain(g) - const y = a.chain(x => f(x).chain(g)) - - assert(x.equals(y), `${algebra.name} is not associative`) - }) - }), -]