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' /** * @template T, E */ 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, E */ export class Ok extends Result { /** @type T */ #value /** @param {T} value */ constructor(value) { super() this.#value = value } /** * @returns {this is Ok} */ isOk() { return true } /** * @returns {this is Err} */ isErr() { return false } /** * @template {Result} R * @param {(value: T) => R} fn * @returns {R} */ andThen(fn) { return fn(this.#value) } /** * @template V * @param {(value: T) => V} fn * @returns {Result} */ map(fn) { return Result.ok(fn(this.#value)) } /** * @template V * @param {V} _defaultValue * @param {(value: T) => V} fn * @returns {V} */ mapOr(_defaultValue, fn) { return this.map(fn) } /** * @template V * @param {() => V} _defaultFn * @param {(value: T) => V} fn * @returns {V} */ mapOrElse(_defaultFn, fn) { return this.map(fn) } /** * @param {Result} other * @returns {Result} */ and(other) { return other } /** * @param {Result} _other * @returns {Result} */ or(_other) { return this } /** * @template F * @param {(error: E) => Result} _fn * @returns {Result} */ orElse(_fn) { return this } /** * @param {Result} other * @returns {boolean} */ equals(other) { return other instanceof Ok && this.#value === other.#value } /** * @param {Result} other * @returns {boolean} */ lte(other) { return other instanceof Ok && 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 {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 T, E */ export class Err extends Result { /** @type E */ #error /** @param {E} error */ constructor(error) { super() this.#error = error } /** * @returns {this is Ok} */ isOk() { return false } /** * @returns {this is Err} */ isErr() { return true } /** * @template {Result} R * @param {(value: T) => R} _fn * @returns {R} */ andThen(_fn) { return this } /** * @template V * @param {(value: T) => V} _fn * @returns {Result} */ map(_fn) { return this } /** * @template V * @param {V} defaultValue * @param {(value: T) => V} _fn * @returns {V} */ mapOr(defaultValue, _fn) { return defaultValue } /** * @template V * @param {() => V} defaultFn * @param {(value: T) => V} _fn * @returns {V} */ mapOrElse(defaultFn, _fn) { return defaultFn() } /** * @template T * @param {Result} _other * @returns {Result} */ and(_other) { return this } /** * @template T * @param {Result} other * @returns {Result} */ or(other) { return other } /** * @template T, F * @param {(error: E) => Result} fn * @returns {Result} */ orElse(fn) { return fn(this.#error) } /** * @template T * @param {Result} other * @returns {boolean} */ equals(other) { return other instanceof Err && this.#error === other.#error } /** * @template T * @param {Result} other * @returns {boolean} */ lte(other) { return other instanceof Err && 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 {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.andThen, map: Ok.prototype.map, reduce: Ok.prototype.reduce, alt: Ok.prototype.or, equals: Ok.prototype.equals, lte: Ok.prototype.lte, }) implementor(Err) .implements(...structures) .specifiedBy(FantasyLand) .with({ chain: Err.prototype.andThen, map: Err.prototype.map, reduce: Err.prototype.reduce, alt: Err.prototype.or, equals: Err.prototype.equals, lte: Err.prototype.lte, })