monads-co
Version:
An implementation of Haskell's type classes in TS
181 lines (157 loc) • 4.66 kB
text/typescript
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)
}