import { Monad, FunctorFunc, ApplicativeFunc, MonadFunc } from "./interfaces"; import { Either } from "./either"; /** * The Maybe abstract class. * @since 0.5.0 */ export abstract class Maybe implements Monad { abstract map (f: (a: T) => U): Maybe; abstract fmap (f: FunctorFunc): Maybe; abstract applies (f: ApplicativeFunc): (mb: Maybe) => Maybe; abstract mbind (f: Maybe>): Maybe; abstract flatten (): Maybe; abstract get (): T | never; abstract getOrElse (f: () => T): T; abstract getOrElseGet (value: T): T; abstract getOrThrow (err?: Error): T | never; abstract orElse (o: () => Maybe): Maybe; abstract toObject (): {just: T | null}; /** * Returns that this is None. * returns {boolean} */ public isDefined (): boolean { return false; } /** * Returns that this is Just. * @returns {boolean} */ public isEmpty (): boolean { return false; } /** * Returns a Either.Left. * @returns {Either.Left} */ public toEither (): Either { return Either.left(new ReferenceError("This either is Left.")); } /** * Tests equality of Maybes. * @param {Maybe} other - the other Maybe to test * @returns {boolean} */ public equals (other: Maybe): boolean { if (! other || other instanceof Maybe === false) return false; if (this === other) return true; if (this.isDefined() === false && other.isDefined() === false) return true; return this.isDefined() === other.isDefined() && this.get() === other.get(); } /** * Returns the Maybe as a JSON object. * @returns {{just: T | null}} */ public toJSON (): {just: T | null} { return this.toObject(); } /** * Returns the Maybe as a string. * @returns {string} '{"just": T | null}' */ public toString (): string { return JSON.stringify(this.toJSON()); } } /** * The Maybe namespace. * @since 0.5.0 */ export namespace Maybe { /** * Returns a new Maybe.Just instance. * @returns {Maybe.just} */ export function just (value: T) { return new Just(value); } /** * Returns a new Maybe.None instance. * @returns {Maybe.None} */ export function none () { return new None(); } /** * Returns the singleton instance of Maybe.None. * @returns {Maybe.None} */ export function nothing () { return nothingMaybe; } /** * Returns a Maybe.Just instance, or Maybe.None for null and undefined. * @returns {Maybe} */ export function fromNull (value: T | undefined | null): Maybe { if (value === null || value === undefined) { return Maybe.none(); } else { return Maybe.just(value); } } /** * Iterates over an Array of Maybe values. If any Maybe.None, the iteration * stops and a Maybe.None is returned. * @since 0.5.0 */ export function sequence (...maybes: Array>): Maybe { const arr = [] as T[]; for (const i in maybes) { if (maybes[i].isEmpty()) { return new None(); } arr[i] = maybes[i].get(); } return new Just(arr); } /** * Maps over an Array with the given function. If the function ever returns * a Maybe.None, the iteration stops and a Maybe.None is returned. * @since 0.5.0 */ export function traverse (f: (a: T) => Maybe): (as: T[]) => Maybe { return function (as: T[]): Maybe { const arr = [] as U[]; for (const i in as) { const r = f(as[i]); if (r.isEmpty()) { return new None(); } arr[i] = r.get(); } return new Just(arr); } } /** * Lifts the given partialFunction into a total function that returns an Maybe result. * Basically, wraps the function in try/catch and returns Maybe.Just() or Maybe.None(). * @param {(...args: any[]) => T} partialFunction - the function to lift * @returns (...args: any[]) => Maybe */ export function lift (partialFunction: (...args: any[]) => T): (...args: any[]) => Maybe { return (...args: any[]) => { try { return Maybe.just(partialFunction.apply(partialFunction, args) as T); } catch (err) { return Maybe.none(); } }; } /** * The Maybe.None class. * @since 0.5.0 */ export class None extends Maybe { constructor () { super(); } /** * * @since 0.5.0 */ public map (f: (a: T) => U): None { return new None(); } /** * * @since 0.5.0 */ public fmap (f: (a: T) => Maybe): None { return new None(); } /** * * @since 0.5.0 */ public applies (f: (a: T) => (b: U) => Maybe): (mb: Maybe) => None { return function (mb: Maybe): None { return new None(); } } /** * * @since 0.5.0 */ public mbind (f: Maybe<(a: T) => Maybe>): None { return new None(); } /** * * @since 0.5.0 */ public flatten (): None { return this; } /** * Returns that this option is empty. * @returns {boolean} */ public isEmpty () { return true; } /** * Throws a ReferenceError. */ public get (): never { throw new ReferenceError("This is option is None.") } /** * Returns the evaluated given function. * @param {() => T} f - the or else function to evaluate * @returns {T} */ public getOrElse (f: () => T): T { return f(); } /** * Returns the T value. * @param {T} value - the or else value * @returns {T} */ public getOrElseGet (value: T): T { return value; } /** * Throws a ReferenceError or the given Error. * @param {Error} [err] - the optional Error to throw * @returns {T} */ public getOrThrow (err?: Error): never { throw err || new ReferenceError("This option is None.") } /** * Returns the evaluated function. * @param {() => Maybe} f - the or else function to evaluate * @returns {Maybe} */ public orElse (f: () => Maybe): Maybe { return f(); } /** * Returns an Either.Left. */ public toEither () { return Either.left(new ReferenceError("This either is Left.")); } /** * Returns the Maybe as a plain-old JS object. * @returns {{just: null}} */ public toObject (): {just: null} { return { just: null }; } } /** * The Maybe.Just class. * @since 0.5.0 */ export class Just extends Maybe { constructor (private value: T) { super(); } /** * * @since 0.5.0 */ public map (f: (a: T) => U): Just { return new Just(f(this.value)); } /** * * @since 0.5.0 */ public fmap (f: (a: T) => Maybe): Maybe { return f(this.value); } /** * * @since 0.5.0 */ public applies (f: (a: T) => (b: U) => Maybe): (mb: Maybe) => Maybe { return mb => mb.fmap(f(this.value)); } /** * * @since 0.5.0 */ public mbind (f: Maybe<(a: T) => Maybe>): Maybe { return this.applies(a => (b: (a: T) => Maybe) => b(a))(f); } /** * If this Maybe is a Maybe of a Maybe, etc., then it will unwrap to the last Maybe. * @since 0.5.0 */ public flatten (): Maybe { const val = this.get(); if (val instanceof Maybe) { return val.flatten() as any; } else { return this as any; } } /** * Returns that this option is Maybe.Just. * @returns {boolean} */ public isDefined () { return true; } /** * Returns the Just value. * @returns {T} */ public get (): T { return this.value; } /** * Returns the Just value. * @param {() => T} f - the function to evaluate if Left * @returns {T} */ public getOrElse (value: () => T): T { return this.value; } /** * Returns the Just value. * @param {T} value - the value to return if None * @return {T} */ public getOrElseGet (value: T): T { return this.value; } /** * Returns the Just value. * @returns {T} */ public getOrThrow (err?: Error): T { return this.value; } /** * Returns this Just value. * @param {Maybe} f - the function to evaluate if Left */ public orElse (o: () => Maybe): Maybe { return this; } /** * Returns an Either.Right. * @returns {Either.Right} */ public toEither () { return Either.right(this.value); } /** * Returns the Maybe as a plain-old JS object. * @returns {{just: T}} */ public toObject (): {just: T} { return { just: this.value }; } } const nothingMaybe = new None(); }