UNPKG

monads-co

Version:

An implementation of Haskell's type classes in TS

181 lines (157 loc) 4.66 kB
import { Monad, MonadHKT } from './Monad' import { None, Optional, Some } from './Optional' export interface ResultHKT<ErrorT> extends MonadHKT { output: Result<this["input"], ErrorT> } export class Result<ValueT, ErrorT> extends Monad<ValueT> { constructor(public _success: boolean, public _value: ValueT | ErrorT) { super() } /** * Used to wrap a potential error throwing function into a safe Result wrapped function. Usage looks like: * ```ts * const safeJsonParse = Result.wrap<typeof JSON.parse, SyntaxError>(JSON.parse) * ``` * @param fn */ static wrap<FnT extends (...args: any[]) => any, ErrorT>(fn: FnT) { return (...args: Parameters<FnT>) => { try { return Ok<ReturnType<FnT>, ErrorT>(fn(...args)) } catch (e) { return Err<ReturnType<FnT>, ErrorT>(e as ErrorT) } } } /** * If Ok, this uses the mapping function to transform Value to a new Value, and returns Result<New Value, ErrorT> * otherwise it propagates the ErrorT. * @param mapping */ map<B>(mapping: (value: ValueT) => B): Result<B, ErrorT> { if (this._success) { return Ok<B, ErrorT>(mapping(this._value as ValueT)) } else { return Err<B, ErrorT>(this._value as ErrorT) } } amap<B>(mapping: Result<(value: ValueT) => B, ErrorT>): Result<B, ErrorT> { if (this._success) { if (mapping._success) { return Ok( (mapping._value as (value: ValueT) => B)(this._value as ValueT) ) } } if (!mapping._success) { return Err(mapping._value as ErrorT) } return Err(this._value as ErrorT) } pure<B>(value: B): Result<B, ValueT> { return Ok(value) } /** * If Ok, the mapping of ValueT to a Result is run, otherwise the Error is propagated. If you want to just transform * ValueT to a new Value, use `.map` * @param mapping */ then<B>(mapping: (value: ValueT) => Result<B, ErrorT>): Result<B, ErrorT> { if (this._success) { return mapping(this._value as ValueT) } return Err(this._value as ErrorT) } // Result specific methods /** * Returns the ValueT if Ok, otherwise it returns `defaultValue` * @param defaultValue */ or<B>(defaultValue: B): ValueT | B { if (this._success) { return this._value as ValueT } return defaultValue } /** * If Err this runs the mapping from ErrorT => ValueT, otherwise it returns a new Result containing ValueT * @param mapping */ catch<NewErrorT>(mapping: (value: ErrorT) => ValueT): Result<ValueT, NewErrorT> { if (this._success) { return Ok(this._value as ValueT) } return Ok(mapping(this._value as ErrorT)) } /** * If Err this runs the mapping from ErrorT => Result<ValueT, NewErrorT>, otherwise it returns a new Result containing * ValueT * @param mapping */ catchThen<NewErrorT>(mapping: (value: ErrorT) => Result<ValueT, NewErrorT>): Result<ValueT, NewErrorT> { if (this._success) { return Ok(this._value as ValueT) } return mapping(this._value as ErrorT) } /** * Returns the ValueT if Ok, otherwise it throws ErrorT. This is the nuclear option, `.or(default)` should be preferred. */ unwrap(): ValueT { if (this._success) { return this._value as ValueT } // Kaboom, functional land just died :( throw this._value } /** * Converts a Result<ValueT, ErrorT> to an Optional<ValueT> */ toOptional(): Optional<ValueT> { if (this._success) { return Some(this._value as ValueT) } else { return None() } } } /** * Used to construct an Ok value, for example: * * @example * ```ts * type ErrorMessage = string * * function safeDivide(a: number, b: number): Result<number, ErrorMessage> { * if (b === 0) { * return Err("Can't divide by zero") * } else { * return Ok(a / b) * } * } * ``` * @param value */ export function Ok<ValueT, ErrorT>(value: ValueT) { return new Result<ValueT, ErrorT>(true, value) } /** * Used to construct an Err value, for example: * * @example * ```ts * type ErrorMessage = string * * function safeDivide(a: number, b: number): Result<number, ErrorMessage> { * if (b === 0) { * return Err("Can't divide by zero") * } else { * return Ok(a / b) * } * } * ``` * @param value */ export function Err<ValueT, ErrorT>(value: ErrorT) { return new Result<ValueT, ErrorT>(false, value) }