text: 
  const _        = require('ramda');
  const Bluebird = require('bluebird');
  const v        = require('validator');
  
  const MSG  = {
    REQUIRED                : 'required'
  , REQUIRE_ARRAY           : 'require_array'
  , CANNOT_BE_EQUAL         : 'cannot_be_equal'
  , NOT_INT                 : 'not_int'
  , NOT_STRING              : 'not_string'
  , NOT_FUNCTION            : 'not_function'
  , NOT_ARRAY               : 'not_array'
  , LIST_ITEM_NOT_SPEC      : 'list_item_not_spec'
  , NOT_NULLABLE            : 'cannot_be_null'
  , IMMUTABLE               : 'cannot_change_immutable'
  , NOT_IN_RANGE            : 'not_in_range'
  , NOT_IN                  : 'not_in'
  , NOT_BOOL                : 'not_bool'
  , MINIMUM_NOT_MET         : 'minimum_not_met'
  , MAXIMUM_EXCEEDED        : 'maximum_exceeded'
  , MINIMUM_LENGTH_NOT_MET  : 'min_length_not_met'
  , MAXIMUM_LENGTH_EXCEEDED : 'maximum_length_exceeded'
  , DOES_NOT_MATCH          : 'does_not_match'
  , INVALID_EMAIL           : 'invalid_email'
  };
  
  
  // basic : (a -> Bool), String -> (a -> Maybe String)
  const basic = (test, msg) => (...args) => {
    return test.apply(null, args) ? null : msg
  }
  
  // extern : (a -> Bool), String -> (a -> Maybe String)
  const extern = (test, msg) => _.composeP(
    basic(_.equals(true), msg)
  , _.when(_.isNil, _.T)
  , _.compose(Bluebird.resolve, test)
  );
  
  
  // required : a -> Maybe String
  const required = basic(
    _.compose(_.not, _.either(_.isEmpty, _.isNil))
  , MSG.REQUIRED
  );
  required.always_run = true;
  
  
  // requireArray : a -> Maybe String
  const requireArray = basic(_.is(Array), MSG.REQUIRE_ARRAY);
  requireArray.always_run = true;
  
  
  // notEqual : a -> Maybe String
  const notEqual = (x) => basic(
    _.compose(_.not, _.equals(x))
  , MSG.CANNOT_BE_EQUAL
  );
  
  
  // isInt : a -> Maybe String
  const isInt = basic(_.unary(v.isInt), MSG.NOT_INT);
  
  
  // isString : a -> Maybe String
  const isString = basic(_.is(String), MSG.NOT_STRING);
  
  
  // isFunction : a -> Maybe String
  const isFunction = basic(_.is(Function), MSG.NOT_FUNCTION);
  
  
  // isArray : a -> Maybe String
  const isArray = basic(_.is(Array), MSG.NOT_ARRAY);
  
  
  // listOf : (a -> Bool), String -> Array a -> Promise Maybe String
  const listOf  = (check, msg) => (list) => {
  
    const validator = extern(check, msg || MSG.LIST_ITEM_NOT_SPEC);
    const reducer   = (result, item) => result ? result : validator(item);
  
    return Bluebird.reduce(list, reducer, null);
  };
  
  
  // notNullable : a -> Maybe String
  const notNullable = basic(
    (value, request, current, key) => { return _.all(
      _.compose(_.not, _.equals(null))
      , [ value, _.prop(key, current) ]
      )
    }
    , MSG.NOT_NULLABLE
  );
  
  // immutable : a -> Maybe String
  const immutable = basic(
    (value, request, current, key) => { return _.propEq(key, value, current) }
    , MSG.IMMUTABLE
  );
  
  
  // _satisfies : Function -> [ String ] -> Bool
  const _satisfies = (condFn) => (keys) => _.compose(
    _.not
    , condFn(_.compose(_.not, _.isNil)),
    _.props(keys)
  )
  
  
  // reqIf : Function -> [ String ] -> Maybe String
  const _reqIf = (condFn) => (keys) => basic(
    _.useWith(_.or, [ _.identity , _satisfies(condFn)(keys) ])
    , MSG.REQUIRED
  )
  
  
  // reqIfHasAny : [ String ] -> a , Request -> Maybe String
  const reqIfHasAny = _reqIf(_.any);
  
  
  // reqIfHasAll : [ String ] -> a , Request -> Maybe String
  const reqIfHasAll = _reqIf(_.all);
  
  
  // notReqIfHasAny : [ String ] -> a , Request -> Maybe String
  const notReqIfHasAny = _reqIf(_.curryN(2, _.compose(_.not, _.any)))
  
  
  // notReqIfHasAll : [ String ] -> a , Request -> Maybe String
  const notReqIfHasAll = _reqIf(_.curryN(2, _.compose(_.not, _.all)))
  
  
  // _isInRangeInc : Int, Int -> (Int -> Bool)
  const _isBetweenInc = (min, max) => _.allPass([ _.lte(min), _.gte(max) ])
  
  
  // _isBetween : Int -> Maybe String
  const _betweenIf = _.ifElse(
    _.identity
  , _.always(null)
  , _.always(MSG.NOT_IN_RANGE)
  );
  
  
  // isLength : Int, Int -> (a -> Maybe String)
  const isLength = (min, max) => _.compose(
    _betweenIf
  , _isBetweenInc(min, max)
  , _.length
  )
  
  
  // Convert the string to an array to accomodate for unicode symbols, that count
  // as more than one character in type String, but should be counted only as one.
  // This funciton behaves exactly as isLength, but accounts for Unicode
  // isLengthUnicode : Int, Int -> (String -> Maybe String)
  const isLengthUnicode = (min, max) => (str) => {
    return isLength(min, max)(Array.from(str))
  }
  
  
  // isIn :: Array -> * -> Maybe String
  const isIn = (options) => _.ifElse(
    _.flip(_.contains)(options)
  , _.always(null)
  , _.always(MSG.NOT_IN)
  )
  
  // isBool :: Integer -> Maybe String
  const isBool = _.ifElse(
   _.either(
    _.flip(_.contains)([true, false]),
    _.compose(
      _.flip(_.contains)([0,1]),
      (i) => parseInt(i, 10)
    )
   )
  , _.always(null)
  , _.always(MSG.NOT_BOOL)
  )
  
  // isInRangeInclusive :: Integer, Integer -> value -> Maybe String
  const isInRangeInclusive = (min, max) => _.ifElse(
    _.compose(
      _isBetweenInc(min, max)
    , (i) => parseInt(i,10)
    )
  , _.always(null)
  , _.always(MSG.NOT_IN_RANGE)
  )
  
  // _isNot :: a -> (a -> Bool)
  const _isNot = (val) => _.compose(_.not, _.equals(val))
  
  // defined "" a -> Maybe String
  const defined = basic( _isNot(undefined), MSG.REQUIRED)
  
  // min :: Integer -> Integer -> Maybe String
  const min = basic(_.lte, MSG.MINIMUM_NOT_MET)
  
  // max :: Integer -> Integer -> Maybe String
  const max = basic(_.gte, MSG.MAXIMUM_EXCEEDED)
  
  // _isAtLeastMin :: Integer -> String -> Bool
  const _isAtLeastMin = (min, text) => _.compose(_.lte(min), _.length)(text)
  
  // minLength :: Integer -> String -> Maybe String
  const minLength = _.curryN(2, basic(_isAtLeastMin, MSG.MINIMUM_LENGTH_NOT_MET))
  
  // _isLessThanMax :: Integer -> String -> Bool
  const _isLessThanMax = (max, text) => _.compose(_.gte(max), _.length)(text)
  
  // maxLength :: Integer -> String -> Maybe String
  const maxLength = _.curryN(2, basic(_isLessThanMax, MSG.MAXIMUM_LENGTH_EXCEEDED))
  
  
  // isEqualTo :: String -> Value, Object -> Maybe String
  const isEqualTo = _.curryN(3, basic(_.propEq, MSG.DOES_NOT_MATCH))
  
  // checkEmail :: String -> Bool
  const checkEmail = (val) => (v.isEmail(val))
  
  // isValidEmail :: String -> Maybe String
  const isValidEmail = basic(checkEmail, MSG.INVALID_EMAIL)
  
  module.exports = {
    MSG
  , extern
  , required
  , requireArray
  , notEqual
  , isInt
  , isString
  , isFunction
  , isArray
  , listOf
  , notNullable
  , immutable
  , reqIfHasAny
  , reqIfHasAll
  , notReqIfHasAny
  , notReqIfHasAll
  , isLength
  , isLengthUnicode
  , isIn
  , isBool
  , isInRangeInclusive
  , defined
  , min
  , max
  , minLength
  , maxLength
  , isEqualTo
  , isValidEmail
  };
  