UNPKG

monads-co

Version:

An implementation of Haskell's type classes in TS

177 lines (153 loc) 4.32 kB
import { Monad, MonadHKT } from './Monad' export interface OptionalHKT extends MonadHKT { output: Optional<this["input"]> } export class Optional<ValueT> extends Monad<ValueT> { constructor(public _success: boolean, public _value: ValueT | undefined | null) { super() } /** * Used to wrap a function that can potentially return null or undefined in a safe wrapper * ```ts * const safeQuerySelector = Optional.wrap(document.querySelector) * ``` * @param fn */ static wrap<FnT extends (...args: any[]) => any>(fn: FnT) { return (...args: Parameters<FnT>) => { const out = fn(...args) if (out === undefined || out === null) { return None<NonNullable<ReturnType<FnT>>>() } else { return Some<NonNullable<ReturnType<FnT>>>(out) } } } /** * If a value is held, this uses the mapping function to transform it, and returns Optional<New Value> * otherwise, it returns None() * @param mapping */ map<B>(mapping: (value: ValueT) => B): Optional<B> { if (this._success) { return Some(mapping(this._value as ValueT)) } else { return None() } } amap<B>(mapping: Optional<(value: ValueT) => B>): Optional<B> { if (this._success) { if (mapping._success) { return Some( (mapping._value as (value: ValueT) => B)(this._value as ValueT) ) } return None() } return None() } pure<B>(value: B): Optional<B> { return Some(value) } /** * If a value is held the mapping is preformed on it, otherwise None() is returned. If you want to just transform * ValueT to a new Value, use `.map` * @param mapping */ then<B>(mapping: (value: ValueT) => Optional<B>): Optional<B> { if (this._success) { return mapping(this._value as ValueT) } return None() } // Result specific methods /** * Returns the value is defined, otherwise it returns `defaultValue` * @param defaultValue */ or<B>(defaultValue: B): ValueT | B { if (this._success) { return this._value as ValueT } return defaultValue } /** * If None() replace it with a value * @param mapping */ catch<NewErrorT>(mapping: () => ValueT): Optional<ValueT> { if (this._success) { return Some(this._value as ValueT) } return Some(mapping()) } /** * If None() replaces it with the result of the mapping * @param mapping */ catchThen<NewErrorT>(mapping: () => Optional<ValueT>): Optional<ValueT> { if (this._success) { return Some(this._value as ValueT) } return mapping() } /** * Returns the value if Some(), otherwise it throws a reference error. * 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 new ReferenceError("When Optional<T> was unwrapped, no value was found") } } /** * Used to construct Some value, for example: * * @example * ```ts * // Gets all but the start of the string; IE "string" -> "tring" * function getStringTail(input: string): Optional<string> { * if (input.length <= 1) { * return None() * } else { * return Some(input.slice(1)) * } * } * ``` * @param value */ export function Some<ValueT>(value: ValueT) { return new Optional<ValueT>(true, value) } /** * Used to construct Some value, for example: * * @example * ```ts * // Gets all but the start of the string; IE "string" -> "tring" * function getStringTail(input: string): Optional<string> { * if (input.length <= 1) { * return None() * } else { * return Some(input.slice(1)) * } * } * ``` */ export function None<ValueT>() { return new Optional<ValueT>(false, null) } /** * Like Some() or None() but it doesn't mind if you give it a value or not. * @param value */ export function Maybe<ValueT>(value?: ValueT | undefined | null): Optional<NonNullable<ValueT>> { if (value) { return Some(value as NonNullable<ValueT>) } else { return None() } }