/**
* Created by edlc on 12/9/16.
* Contains basic `Monad` class and associated methods.
* For 'what is a monad'/back-story
* @see `Maybe` reference: [http://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Maybe.html](http://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Maybe.html)
* @see `Either` reference: [http://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Either.html](http://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Either.html)
* @module monad
*/
import {isset, curry, instanceOf, trampoline} from 'fjl';
import Applicative from '../functor/Applicative';
export const
/**
* Returns boolean indicating whether given value is an
* instance of monad or not.
* @function module:monad.isMonad
* @param value {*}
* @returns {boolean}
*/
isMonad = value => value && value instanceof Monad,
/**
* Always returns a monad; If given value is not
* a monad creates one using given value.
* @function module:monad.toMonad
* @param x {Monad|*} - Monad or any.
* @returns {*}
*/
toMonad = x => !isMonad(x) ? new Monad(x) : x,
/**
* Calls `valueOf` on value (use for functional composition).
* @function module:monad.valueOf
* @param x {*}
* @returns {*}
*/
valueOf = x => x.valueOf(),
/**
* Calls `valueOf` on given value. Same as
* monadic `join` operation (extracts inner value of
* container/object).
* @function module:monad.join
* @param x {*}
* @returns {*}
*/
join = valueOf,
/**
* Maps given function over given functor.
* @function module:monad.fmap
* @param fn {Function}
* @param x {Functor}
* @returns {Functor}
*/
fmap = curry((fn, x) => x.map(fn)),
/**
* Applies function contained by applicative to contents of given functor.
* (Same as functional applicative `apply`).
* @function module:monad.ap
* @param applicative {Applicative}
* @param functor {Functor}
* @returns {Applicative}
*/
ap = curry((applicative, functor) => applicative.ap(functor)),
/**
* Flat maps a function over given monad's contained value.
* @function module:monad.flatMap
* @param fn {Function}
* @param monad {Monad}
* @returns {Monad}
*/
flatMap = curry((fn, monad) => monad.flatMap(fn)),
/**
* A recursive monad un-wrapper - Returns monad's unwrapped, inner-mostly, contained value (recursively).
* @function module:monad.getMonadUnWrapper
* @param Type {Function}
* @returns {Array.<*>} - [unWrapFunction, tailCallFuncName (used by `trampoline` @see module:fjl.trampoline)]
*/
getMonadUnWrapper = Type => {
return [ function unWrapMonadByType(monad) {
return instanceOf(Type, monad) ?
function trampolineCall() {
return unWrapMonadByType(monad.valueOf());
} :
monad;
}, 'trampolineCall' ];
},
/**
* Unwraps monad by type.
* @function module:monad.unWrapMonadByType
* @param Type {Function}
* @param monad {Monad}
* @returns {*}
*/
unWrapMonadByType = (Type, monad) => {
if (!isset(monad)) {
return monad;
}
const [unWrapper, tailCallName] = getMonadUnWrapper(Type),
unwrap = trampoline(unWrapper, tailCallName);
return unwrap(monad);
};
/**
* @class module:monad.Monad
* @param x {*}
* @property value {*}
* @extends module:functor.Applicative
*/
export default class Monad extends Applicative {
/**
* Monadic join - Removes one layer of monadic structure from value.
* @memberOf module:monad.Monad
* @returns {*}
*/
join () {
return this.valueOf();
}
/**
* Flat map operation.
* @memberOf module:monad.Monad
* @param fn {Function}
* @returns {Monad}
*/
flatMap (fn) {
const out = unWrapMonadByType(this.constructor, fn(this.join()));
return this.constructor.of(out);
}
/**
* Same as `Monad.flatMap`.
* @memberOf module:monad.Monad
* @param fn {Function}
* @returns {Monad}
*/
chain (fn) {
return this.flatMap(fn);
}
/**
* Same as `new Monad(...)` just in 'static' function
* format.
* @memberOf module:monad.Monad
* @static
* @param x {*}
* @returns {Monad}
*/
static of (x) { return new Monad(x); }
}