import { Monad } from './Monad'; import { Either, left, right } from './Either'; /** * just wraps a value in a Just */ export const just = (a: A): Maybe => new Just(a);; /** * nothing constructs nothing */ export const nothing = () => new Nothing(); /** * fromAny constructs a Maybe from a value that may be null. */ export const fromAny = (a: A): Maybe => a == null ? nothing() : just(a); /** * fromArray checks an array to see if it's empty (or full of nulls) * and returns a Maybe. */ export const fromArray = (a: A[]): Maybe => ((a.length === 0) || (a.reduce((c, v) => (v == null) ? c + 1 : c, 0) === a.length)) ? nothing() : just(a) /** * fromOBject uses Object.keys to turn see if an object has any own properties. */ export const fromObject = (o: A): Maybe => Object.keys(o).length === 0 ? nothing() : just(o); /** * fromString constructs nothing if the string is empty or just otherwise. */ export const fromString = (s: string): Maybe => (s === '') ? nothing() : just(s); /** * fromBoolean constructs nothing if b is false, just otherwise */ export const fromBoolean = (b: boolean): Maybe => (b === false) ? nothing() : just(b); /** * fromNumber constructs nothing if n is 0 just otherwise. */ export const fromNumber = (n: number): Maybe => (n === 0) ? nothing() : just(n); /** * isString tests whether the value is a string or not. */ export const isString = (s: any): Maybe => (typeof s === 'string') ? just(s) : nothing(); /** * isBoolean tests whether the value is a boolean or not. */ export const isBoolean = (b: any): Maybe => (typeof b === 'boolean') ? just(b) : nothing(); /** * isTrue constructs nothing if b !== true */ export const isTrue = (b: any): Maybe => (b === true) ? just(b) : nothing(); /** * isFalse constructs nothing if b !== false */ export const isFalse = (b: any): Maybe => (b === false) ? just(b) : nothing(); /** * isNumber tests whether the value is number or not. */ export const isNumber = (n: any): Maybe => (typeof n === 'number') ? just(n) : nothing(); /** * isObject tests whether the value is an object or not. */ export const isObject = (o: any): Maybe => ((!Array.isArray(o)) && (typeof o === 'object')) ? just(o) : nothing(); /** * isArray tests whether the value is an array or not. */ export const isArray = (a: any): Maybe => Array.isArray(a) ? just(a) : nothing(); /** * Maybe */ export abstract class Maybe implements Monad { static just = just; static nothing = nothing; static fromAny = fromAny; static fromObject = fromObject; static fromArray = fromArray; static fromString = fromString; static fromBoolean = fromBoolean static fromNumber = fromNumber; static isNumber = isNumber; static isString = isString; static isArray = isArray; static isBoolean = isBoolean; static isTrue = isTrue; static isFalse = isFalse; static isObject = isObject; of(a: A): Maybe { return new Just(a); } abstract map(_: (a: A) => B): Maybe; abstract chain(_: (a: A) => Maybe): Maybe; abstract get(): A abstract orElse(f: () => Maybe): Maybe; abstract orJust(f: () => B): Maybe; abstract cata(f: () => C, _g: (a: A) => C): C; abstract toEither(): Either } /** * Nothing */ export class Nothing extends Maybe { map(_: (a: A) => B): Maybe { return new Nothing(); } chain(_: (a: A) => Maybe): Maybe { return new Nothing(); } get(): A { throw new TypeError('Cannot get anything from Nothing!'); } orElse(f: () => Maybe): Maybe { return f(); } /** * orJust will turn Nothing into Just, wrapping the value specified. */ orJust(f: () => B): Maybe { return just(f()); } /** * cata applies the corresponding function to the Maybe */ cata(f: () => C, _g: (a: A) => C): C { return f(); } toEither(): Either { return left(undefined); } } /** * Just */ export class Just extends Maybe { constructor(public a: A) { super(); } map(f: (a: A) => B): Maybe { return new Just(f(this.a)); } join(): A { return this.a } chain(f: (a: A) => Maybe): Maybe { return f(this.a); } get(): A { return this.a; } orElse(_f: () => Maybe): Maybe { return this; } orJust(_f: () => B): Maybe { return this; } cata(_f: () => C, g: (a: A) => C): C { return g(this.a); } toEither(): Either { return right(this.a); } }