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' /** * @template T */ 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) } /** * @returns {this is Some} */ isSome() { return this instanceof Some } /** * @returns {this is None} */ isNone() { return this == None || this instanceof _None } } /** @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} */ andThen(fn) { return fn(this.#value) } /** * @param {Option} other * @returns {Option} */ and(other) { return other } /** * @param {Option} other * @returns {Option} */ or(_other) { return this } /** * @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 V * @param {(value: T) => V} fn * @returns {Option} */ map(fn) { return new Some(fn(this.#value)) } /** * @template V * @param {V} _default * @param {(value: T) => V} fn * @returns {V} */ mapOr(_default, fn) { return this.map(fn) } /** * @template V * @param {() => V} _defaultFn * @param {(value: T) => V} fn * @returns {V} */ mapOrElse(_defaultFn, fn) { return this.map(fn) } /** * @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 * @param {Option} _other * @returns {Option} */ and(_other) { return this } /** * @template T * @template {Option} R * @param {(value: T) => R} fn * @returns {R} */ andThen(_fn) { return this } /** * @template T * @param {Option} other * @returns {Option} */ or(other) { return other } /** * @template T * @param {() => Option} other * @returns {Option} */ orElse(fn) { return fn() } /** * @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 V * @template {Option} R * @param {(value: T) => V} fn * @returns {R} */ 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, 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.andThen, filter: Some.prototype.filter, map: Some.prototype.map, alt: Some.prototype.or, equals: Some.prototype.equals, lte: Some.prototype.lte, }) implementor(_None) .implements(...structures) .specifiedBy(FantasyLand) .with({ chain: _None.prototype.andThen, filter: _None.prototype.filter, map: _None.prototype.map, alt: _None.prototype.or, equals: _None.prototype.equals, lte: _None.prototype.lte, })