459 lines
7.1 KiB
JavaScript
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,
|
|
})
|