kojima/src/result.js
2025-10-22 01:49:12 -04:00

459 lines
7.1 KiB
JavaScript

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<T, E>}
*/
isOk() {
return true
}
/**
* @returns {this is Err<T, E>}
*/
isErr() {
return false
}
/**
* @template {Result<T, E>} R
* @param {(value: T) => R} fn
* @returns {R}
*/
andThen(fn) {
return fn(this.#value)
}
/**
* @template V
* @param {(value: T) => V} fn
* @returns {Result<V, E>}
*/
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<T, E>} other
* @returns {Result<T, E>}
*/
and(other) {
return other
}
/**
* @param {Result<T, E>} _other
* @returns {Result<T, E>}
*/
or(_other) {
return this
}
/**
* @template F
* @param {(error: E) => Result<T, F>} _fn
* @returns {Result<T, F>}
*/
orElse(_fn) {
return this
}
/**
* @param {Result<T, E>} other
* @returns {boolean}
*/
equals(other) {
return other instanceof Ok && this.#value === other.#value
}
/**
* @param {Result<T, E>} 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<T, E>} R
* @returns {R}
*/
flatten() {
if (this.#value instanceof Result) {
return this.bind(id)
} else {
return this
}
}
/**
* @returns {Option<T>}
*/
ok() {
return Option.some(this.#value)
}
/**
* @returns {Option<T>}
*/
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<T, E>}
*/
isOk() {
return false
}
/**
* @returns {this is Err<T, E>}
*/
isErr() {
return true
}
/**
* @template {Result<T, E>} R
* @param {(value: T) => R} _fn
* @returns {R}
*/
andThen(_fn) {
return this
}
/**
* @template V
* @param {(value: T) => V} _fn
* @returns {Result<V, E>}
*/
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<T, E>} _other
* @returns {Result<T, E>}
*/
and(_other) {
return this
}
/**
* @template T
* @param {Result<T, E>} other
* @returns {Result<T, E>}
*/
or(other) {
return other
}
/**
* @template T, F
* @param {(error: E) => Result<T, F>} fn
* @returns {Result<T, F>}
*/
orElse(fn) {
return fn(this.#error)
}
/**
* @template T
* @param {Result<T, E>} other
* @returns {boolean}
*/
equals(other) {
return other instanceof Err && this.#error === other.#error
}
/**
* @template T
* @param {Result<T, E>} 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<T, E>} R
* @returns {R}
*/
flatten() {
if (this.#error instanceof Err) {
return this.bind(id)
} else {
return this
}
}
/**
* @returns {Option<T>}
*/
ok() {
return None
}
/**
* @returns {Option<T>}
*/
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,
})