monads/Maybe.js

import Just from './Just'
import Nothing from './Nothing'

/**
 * @class Maybe
 * @desc Maybe monad provides a way to safely wrap null values
 * @param {*} val - Value to be wrapped
 * @constructor
 * @example
 *
 * const m = Maybe.of(props.customers).map(contract => customer[0]).getOrElse('No customer found')
 */
let Maybe = function (val) {
  this.__value = val
}

/**
 * @memberOf Maybe
 * @function Maybe.of
 * @desc Creates a Maybe monad from the provided argument
 * @param {*} val - Val to turn in to a Maybe monad, can be a function
 * @returns {Maybe}
 * @example
 *
 * const m = Maybe.of(props.customers).map(customer => customer[0]).getOrElse('No customer found')
 */

Maybe.of = function (val) {
  if (val instanceof Maybe) return val
  if (this instanceof Maybe) {
    this.__value = val
    return this
  }
  return new Maybe(val)
}

/**
 * @memberOf Maybe
 * @property {String} type - Returns the string 'Maybe' for all Maybe objects
 * @type {String}
 * @example
 *
 * const m =  Maybe.of([[1,2],[2,3],[4,5]])
 * m.type === 'Maybe'
 */
Maybe.prototype.type = 'Maybe'

/**
 * @memberOf Maybe
 * @function Maybe#getOrElse
 * @desc Evaluates the Maybe monad and returns either the value from the Maybe or the default value
 * @param {*} n - Default value to return if the Maybe evaluates to undefined or null
 * @returns {*}
 * @example
 *
 * // Get customer from component props
 * const m = Maybe.of(props.customers).map(customer => customer.filter(custObj => custObj.id = 'c001112')).getOrElse([])
 * // Map over the provided array
 * m.map(x => <div>Customer ID: {x.id} </div>)
 *
 */
Maybe.prototype.getOrElse = function (n) {
  return this.isNothing() ? n : this.__value
}

/**
 * @memberOf Maybe
 * @function Maybe#map
 * @param {Function} f
 * @returns {Maybe}
 */
Maybe.prototype.map = function (f) {
  if (this.isNothing()) {
    return Maybe.of(null)
  }
  return Maybe.of(f(this.__value))
}

/**
 * @memberOf Maybe
 * @function Maybe#flatMap
 * @param {Function} f
 * @returns {Maybe}
 */
Maybe.prototype.flatMap = function (f) {
  return this.map(f)
}

/**
 * @memberOf Maybe
 * @function Mayeb#ap
 * @param {Maybe} m
 * @returns {Maybe}
 */
Maybe.prototype.ap = function (m) {
  return Maybe.of(m).map(this.__value)
}

/**
 * @memberOf Maybe
 * @function Maybe#isNothing
 * @returns {boolean}
 */
Maybe.prototype.isNothing = function () {
  return (this.__value == null)
}

/**
 * @memberOf Maybe
 * @function Maybe#isJust
 * @returns {boolean}
 */
Maybe.prototype.isJust = function () {
  return !this.isNothing()
}

/**
 * @private
 * @memberOf Maybe
 * @function Maybe#Nothing
 * @returns {Nothing}
 * @constructor
 */
Maybe.prototype.Nothing = function () {
  return new Nothing()
}

/**
 * @private
 * @memberOf Maybe
 * @function Maybe#Just
 * @returns {Just}
 * @constructor
 */
Maybe.prototype.Just = function () {
  return new Just(this._value)
}

export default Maybe