import { Monad, FunctorFunc, ApplicativeFunc, MonadFunc } from "./interfaces"; import { Maybe } from "./maybe"; import { Option } from "./option"; /** * The Either abstract class. */ export abstract class Either implements Monad { abstract map (f: (a: R) => S): Either; abstract fmap (f: FunctorFunc): Either; abstract applies (f: ApplicativeFunc): (mb: Either) => Either; abstract mbind (f: Either>): Either; abstract bimap (lf: (l: L) => M, rf: (r: R) => S): Either; abstract cata (lf: (l: L) => V, rf: (r: R) => V): V; abstract flatten (): Either ; abstract get (): R | never; abstract getOrElse (f: () => R): R; abstract getOrElseGet (right: R): R; abstract getOrThrow (err?: Error): R | never; abstract orElse (f: () => Either): Either; abstract toObject (): {left?: L; right?: R}; /** * Is this a Left. * @returns {boolean} */ public isLeft (): boolean { return false; } /** * Is this a Right. * @returns {boolean} */ public isRight (): boolean { return false; } /** * Returns an undefined Left value. * @returns {L} */ public getLeft (): L | never { throw new ReferenceError("This either is Right."); } /** * Returns an undefined Right value. * @returns {R} */ public getRight (): R | never { throw new ReferenceError("This either is Left."); } /** * Returns an Maybe.None. * @returns {Maybe} * @since 0.5.0 */ public toMaybe (): Maybe { return Maybe.none(); } /** * Returns an Option.None. * @returns {Option} * @deprecated */ public toOption (): Option { return Option.none(); } /** * Tests equality of Eithers. * @params {Either} other - the other Either to test * @returns {boolean} */ public equals (other: Either): boolean { if (this === other) return true; if (! other || other instanceof Either === false) return false; if (this.isRight() !== other.isRight()) return false; if (this.isRight() && this.getRight() === other.getRight()) return true; if (this.getLeft() && this.getLeft() === other.getLeft()) return true; return false; } /** * Returns the Either as a JSON object. * @returns {{left?: L; right?: R}} */ public toJSON (): {left?: L; right?: R} { return this.toObject(); } /** * Returns the Either as a string. * @returns {string} '{"right": R}' or '{"left": L}' */ public toString (): string { return JSON.stringify(this.toJSON()); } } /** * The Either namespace. */ export namespace Either { /** * Returns a new Either.Left instance. * @returns {Either.Left} */ export function left (left: L) { return new Left(left); } /** * Returns a new Either.Right instance. * @returns {Either.Right} */ export function right (right: R) { return new Right(right); } /** * Returns the singleton instance of Either.Left. * @returns {Either.Left} */ export function nothing (): Left { return nothingEither; } /** * Iterates over an Array of Either values. If any Either.Left, the iteration * stops and a Maybe.Left is returned. * @since 0.5.0 */ export function sequence (...eithers: Array>): Either { const arr = [] as R[]; for (const i in eithers) { if (eithers[i].isLeft()) { return new Left(eithers[i].getLeft() as L); } arr[i] = eithers[i].get(); } return new Right(arr); } /** * Maps over an Array with the given function. If the function ever returns * an Either.Left, the iteration stops and a Either.Left is returned. * @since 0.5.0 */ export function traverse (f: (a: R) => Either): (as: R[]) => Either { return function (as: R[]): Either { const arr = [] as S[]; for (const i in as) { const r = f(as[i]); if (r.isLeft()) { return new Left(r.getLeft() as L); } arr[i] = r.get(); } return new Right(arr); } } /** * Lifts the given partialFunction into a total function that returns an Either result. * Basically, wraps the function in try/catch and return Either.Right() or Either.Left(). * @param {(...args: any[]) => T} partialFunction - the function to lift * @returns (...args: any[]) => Either */ export function lift (partialFunction: (...args: any[]) => T): (...args: any[]) => Either { return (...args: any[]) => { try { return Either.right(partialFunction.apply(partialFunction, args) as T); } catch (err) { return Either.left(err); } }; } /** * The Either.Left class. */ export class Left extends Either { constructor (private left: L) { super(); } /** * * @since 0.5.0 */ public map (f: (a: R) => S): Left { return new Left(this.left); } /** * * @since 0.5.0 */ public fmap (f: (a: R) => Either): Left { return new Left(this.left); } /** * * @since 0.5.0 */ public applies (f: (a: R) => (b: S) => Left): (mb: Either) => Left { return function (mb: Either): Left { return new Left(this.left); } } /** * * @since 0.5.0 */ public mbind (f: Either Either>): Left { return new Left(this.left); } /** * * @since 0.5.0 */ public bimap (lf: (l: L) => M, rf: (r: R) => S): Left { return new Left(lf(this.left)); } /** * * @since 0.5.0 */ public cata (lf: (l: L) => V, rf: (r: R) => V): V { return lf(this.left); } /** * * @since 0.5.0 */ public flatten (): Left { return this; } /** * Returns that this is a Left. * @returns {boolean} */ public isLeft (): boolean { return true; } /** * Throws a ReferenceError. */ public get (): never { throw new ReferenceError("This either is Left."); } /** * Returns the Left value. * @returns {L} */ public getLeft (): L { return this.left; } /** * Returns the evaluated given function. * @param {() => R} f - the or else function to evaluate * @returns {R} */ public getOrElse (f: () => R): R { return f(); } /** * Returns the given R value. * @param {R} right - the or else value * @returns {R} */ public getOrElseGet (right: R): R { return right; } /** * Throws a ReferenceError or the given Error. * @param {Error} [err] - the optional Error to throw * @returns {R} */ public getOrThrow (err?: Error): never { throw err || new ReferenceError("This either is Left.") } /** * Returns the evaluated function. * @param {() => Either} f - the or else function to evaluate * @returns {Either} */ public orElse (f: () => Either): Either { return f(); } /** * Returs the Either as a plain-old JS object. * @returns {{left: L}} */ public toObject (): {left?: L; right?: R} { return { left : this.left }; } } /** * The Either.Right class. */ export class Right extends Either { constructor (private right: R) { super(); } /** * * @since 0.5.0 */ public map (f: (a: R) => S): Right { return new Right(f(this.right)); } /** * * @since 0.5.0 */ public fmap (f: FunctorFunc): Either { return f(this.right) as any; } /** * * @since 0.5.0 */ public applies (f: ApplicativeFunc): (eb: Either) => Either { return eb => eb.fmap(f(this.right)); } /** * * @since 0.5.0 */ public mbind (f: Either>): Either { return this.applies(a => (b: FunctorFunc) => b(a))(f); } /** * * @since 0.5.0 */ public bimap (lf: (l: L) => M, rf: (r: R) => S): Right { return new Right(rf(this.right)); } /** * * @since 0.5.0 */ public cata (lf: (l: L) => V, rf: (r: R) => V): V { return rf(this.right); } /** * * @since 0.5.0 */ public flatten (): Either { const val = this.get(); if (val instanceof Either) { return val.flatten() as any; } else { return this as any; } } /** * This is a Right. * @returns {boolean} */ public isRight (): boolean { return true; } /** * Returns the Right value. * @returns {R} */ public get (): R { return this.right; } /** * Returns the Right value. * @returns {R} */ public getRight (): R { return this.right; } /** * Returns the Right value. * @param {() => R} f - the function to evaluate if Left * @returns {R} */ public getOrElse (f: () => R): R { return this.right; } /** * Returns the Right value. * @param {R} right - the value to return if Left * @return {R} */ public getOrElseGet (right: R): R { return this.right; } /** * Returns the Right value. * @returns {R} */ public getOrThrow (): R { return this.right; } /** * Returns this Right. * @param {Either} f - the function to evaluate if Left * @returns {Either} */ public orElse (f: () => Either): Either { return this; } /** * Returns an Maybe.Just. * @returns {Maybe} * @since 0.5.0 */ public toMaybe (): Maybe { return Maybe.just(this.right); } /** * Returns an Option.Some. * @returns {Option} * @deprecated */ public toOption (): Option { return Option.some(this.right); } /** * Returs the Either as a plain-old JS object. * @returns {{left: L | undefined; right: R | undefined;}} */ public toObject (): {left?: L; right?: R} { return { right: this.right }; } } } const nothingEither = new Either.Left(void(0));