maybe-just-maybe

A Maybe monad specifically designed for use with Ramda

maybe-just-maybe conforms to the Monad algebra as described by The Fantasy Land specification. It implements not only the Monad specification but also Alt, Alternative, Applicative, Apply, Chain, Functor, Monoid, Plus, Semigroup, and Setoid. The Maybe monad provides a container for a value that may or may not exist. The Maybe.Just type is used for values that do exist, and the Maybe.Nothing type is used for values that do not exist. Wrapping a value with Maybe allows you to perform functions using the Maybe interface that is designed to behave predictably despite the existence without having to worry about whether the value for that instance exists yet. If your Maybe instance is a Nothing, the function performed on that instance will return another Nothing instance. If your Maybe instance is a Just, the function performed on that instance will return another Just wrapping the result of evaluating that function.

Table of Contents

Installation

This module is distributed via npm:

npm install --save maybe-just-maybe

or

yarn add maybe-just-maybe

Usage

Since maybe-just-maybe is designed for use with Ramda, the following example shows a simple example of how we could use this Maybe monad with Ramda.

// doubleIt :: Number -> Number
const doubleIt = R.multiply(2);

// maybeNumber :: a -> Maybe Number
const maybeNumber = R.ifElse(
  R.is(Number),
  Maybe.of,
  Maybe.Nothing
);

// justDoubleIt :: Maybe Number -> Maybe Number
const justDoubleIt = Maybe.of(doubleIt).ap;

// safeDouble :: a -> Maybe Number
const safeDouble = R.compose(justDoubleIt, maybeNumber);

safeDouble(10) // evaluates as Just 20, a Just containing the result of doubling 10
safeDouble(null) // evaluates as Nothing, since safeNumber(null) is Nothing
safeDouble([ 1, 2 ]) // evaluates as Nothing when given an array
safeDouble('one hundred') // evaluates as Nothing when given a string
safeDouble(() => 99) // evaluates as Nothing when given a function

First we define the doubleIt function. Because it is built using R.multiply it is fairly safe to call the function with any value already. Evaluating doubleIt(4) will give a result of 8, evaluating doubleIt(undefined) will give a result of null. Same goes for doubleIt('one million'), doubleIt([3, 4, 5]), or doubleIt({value: 99}). But it may be surprising to discover that evaluating doubleIt(null) results in a value of 0.

Next we define maybeNumber as a point-free function that will wrap any value we give it in a Maybe. If the specified argument is a number, maybeNumber will return a Just containing that number. Otherwise maybeNumber will return a Nothing. We can be confident that maybeNumber will always return a Maybe, and that Maybe will only ever be a Just if the wrapped value is a number.

Next we define justDoubleIt as a function that wraps our doubleIt function in a Maybe so that we can apply this Maybe instance to other Maybe instances by way of its ap method. Since our example is only interested in applying this Maybe function, we have explicitly exposed only the ap method of our Maybe from justDoubleIt. Whereas doubleIt expects its argument to be a number, justDoubleIt expects to be called with a Maybe instance, and will never return a Just when called with any value that is not a Maybe.

We are now ready to define our safeDouble function as the composition of justDoubleIt being applied to the result of maybeNumber. This composition works because maybeNumber always returns a Maybe and justDoubleIt.ap expects a Maybe.

Calling safeDouble with any argument will always return a Maybe Number. If it is called with a numeric argument then safeDouble will return a Just containing a number resulting from doubling the specified number. Otherwise it returns a Nothing. We can continue composing other more complex functions that consume Maybe instances with the understanding that the Maybe rules for handling Just values and Nothing values allow the applied functions to assume that they will only be applied when the Maybe instance is a Just.

API

Constructors

Just :: a -> Maybe a

creates a Maybe instance of type Just that contains the specified value

arguments:

Nothing :: () -> Maybe a

creates a Maybe instance of type Nothing that contains no value

arguments: none

aliases:

asyncOf :: Promise a -> Maybe a

creates a Maybe instance that transforms from its initial Nothing type to a Just type that contains the value obtained from the specified Promise when it resolves

arguments:

Instance methods and standalone counterparts

alt :: Alt Maybe

returns the first available Just, or returns Nothing if neither Maybe is a Just.

arguments:

alias:

example:

const { alt, altU } = Maybe

const a = Maybe.of(1)
const b = Maybe.of(2)
const n = Maybe.Nothing()

// instance method examples
a.alt(b) // Just 1
a.alt(n) // Just 1
n.alt(b) // Just 2
Maybe.Nothing().alt(n) // Nothing

// curried examples
alt(a)(b) // Just 1
alt(a)(n) // Just 1
alt(n)(b) // Just 2
alt(Maybe.Nothing())(n) // Nothing

// uncurried examples
altU(a, b) // Just 1
altU(a, n) // Just 1
altU(n, b) // Just 2
altU(Maybe.Nothing(), n) // Nothing

ap :: Apply Maybe

applies a Maybe of a function to a Maybe of a value in order to produce a Maybe containing the result of that evaluation.

arguments:

example:

const { ap, apU } = Maybe

const a = Maybe.of(3)
const f = Maybe.of(R.multiply(2))
const n = Maybe.Nothing()

// instance method examples
a.ap(f) // Just 6
f.ap(a) // Just 6
f.ap(n) // Nothing
n.ap(a) // Nothing

// curried examples
ap(a)(f) // Just 6
ap(f)(a) // Just 6
ap(f)(n) // Nothing
ap(n)(a) // Nothing

// uncurried examples
apU(a, f) // Just 6
apU(f, a) // Just 6
apU(f, n) // Nothing
apU(n, a) // Nothing

chain :: Chain Maybe

applies a function to the value inside a Maybe in order to return the Maybe produced by that evaluation. If the function does not return a Maybe value then chain will return a Nothing.

arguments:

alias:

example:

const { chain, chainU } = Maybe

const items = Maybe.of([
  { value: 'cat', type: 'pet', active: true },
  { value: 'flower', type: 'pot', active: false },
  { value: 'dog', type: 'pet', active: true },
  { value: 'bird', type: 'pet', active: false },
  { value: 'cherry', type: 'pit', active: true }
])

const getActives = R.filter(R.propEq('active', true))
const getPets = R.filter(R.propEq('type', 'pet'))
const findCs = R.compose(R.test(/^c/), R.prop('value'))
const getCs = R.filter(findCs)

const isActive = R.compose(Maybe.of, getActives)
const isPet = R.compose(Maybe.of, getPets)
const startsWithC = R.compose(Maybe.of, getCs)

// curried chain functions
const cChain = chain(startsWithC)
const petChain = chain(isPet)
const activeChain = chain(isActive)
const activeCPets = R.compose(cChain, petChain, activeChain)

// 3 ways to get all items of type 'pet'
items.chain(isPet)            // instance method
petChain(items)               // curried
chainU(isPet, items)          // uncurried
// result in Just [ { value: 'cat'... }, { value: 'dog'... }, { value: 'bird'... } ]

// 3 ways to get all active pets that start with 'c'
items.chain(startsWithC)
  .chain(isPet)
  .chain(isActive)            // instance methods
activeCPets(items)            // curried
chainU(isPet, 
  chainU(isActive, 
    chainU(startsWithC, items)
  )
)                             // uncurried
//result in Just [ { value: 'cat', type: 'pet', active: true } ]

// 3 examples of chains from a Nothing
// that all result in a Nothing
Maybe.Nothing()
  .chain(startsWithC)
  .chain(isPet)
  .chain(isActive)            // instance methods
activeCPets(Maybe.Nothing())  // curried
chainU(isPet, 
  chainU(isActive, 
    chainU(startsWithC, Maybe.Nothing())
  )
)                             // uncurried

concat :: Semigroup a

allows the values of matching types contained by 2 Maybe instances to be concatenated using Ramda's R.concat. If either instance is a Nothing then concat will result in a Maybe equal to the non-Nothing instance. If both instances are Nothing, then concat will result in a Nothing. If the values contained by the 2 Maybe instances are not of the same type, then concat will result in a Nothing.

arguments:

alias:

example:

const { concat, concatU } = Maybe

const a = Maybe.of([1, 3, 5])
const b = Maybe.of([6, 8])
const c = Maybe.of([9])

// instance method examples
a.concat(b) // Just [1, 3, 5, 6, 8]
a.concat(b).concat(c) // Just [1, 3, 5, 6, 8, 9]
a.concat(b.concat(c)) // Just [1, 3, 5, 6, 8, 9]
a.concat(Maybe.Nothing()).concat(c) // Just [1, 3, 5, 9]

// curried examples
const concatWithA = concat(a)
const concatWithB = concat(b)
concatWithA(b) // Just [ 1, 3, 5, 6, 8 ]
concat(concatWithA(b))(c) // Just [ 1, 3, 5, 6, 8, 9 ]
concatWithA(concatWithB(c)) // Just [ 1, 3, 5, 6, 8, 9 ]
concat(concatWithA(Maybe.Nothing()))(c) // Just [ 1, 3, 5, 9 ]

// uncurried examples
concatU(a, b) // Just [ 1, 3, 5, 6, 8 ]
concatU(concatU(a, b), c) // Just [ 1, 3, 5, 6, 8, 9 ]
concatU(a, concatU(b, c)) // Just [ 1, 3, 5, 6, 8, 9 ]
concatU(concatU(a, Maybe.Nothing()), c) // Just [ 1, 3, 5, 9 ]

either

accepts a left function and a right function and evaluates one of those functions based on whether the Maybe instance is a Nothing or a Just. If the Maybe is a Nothing then the result of evaluating the left function is returned. If the Maybe is a Just then the result of evaluating the right function with the Maybe's value is returned.

arguments:

example:

const { either, eitherU } = Maybe

const j = Maybe.of('the quick brown fox')
const n = Maybe.Nothing()

const left = R.always([])
const right = R.split(' ')

// instance method examples
j.either(left, right) // ['the', 'quick', 'brown', 'fox']
n.either(left, right) // []

// curried examples
const splitter = either(left, right)
splitter(j) // ['the', 'quick', 'brown', 'fox']
splitter(n) // []

// uncurried examples
eitherU(left, right, j) // [ 'the', 'quick', 'brown', 'fox' ]
eitherU(left, right, n) // []

empty :: Maybe a ~> () -> Maybe a

returns a Maybe instance of type Nothing regardless of the type of the original Maybe

arguments: none

example:

const j = Maybe.of('any value')
const n = Maybe.Nothing()

j.empty() // Nothing
n.empty() // Nothing

equals :: Setoid a

tests the equality of the values contained by 2 Maybe instances

arguments:

alias:

example:

const { equals, equalsU } = Maybe

const a = Maybe.of(42)
const b = Maybe.of(6).map(x => x * 7)
const c = Maybe.Nothing()

// instance method examples
a.equals(b) // true
a.equals(c) // false
c.equals(b) // false
c.equals(Maybe.Nothing()) // true

// curried examples
const equalsA = equals(a)
const equalsC = equals(c)
equalsA(b) // true
equalsA(c) // false
equalsC(b) // false
equalsC(Maybe.Nothing()) // true

// uncurried examples
equalsU(a, b) // true
equalsU(a, c) // false
equalsU(c, b) // false
equalsU(c, Maybe.Nothing()) // true

is

tests the type of the value contained by a Maybe using Ramda's R.is. One notable difference from R.is is that Maybe.of(x).is(Object) will return false if x is an Array, a Function, or a Date.

arguments:

example:

const { is, isU } = Maybe

// instance method examples
Maybe.of('a string').is(String) // true
Maybe.of(13).is(Number) // true
Maybe.of(true).is(Number) // false
Maybe.of([ 1, 2, 3 ]).is(Array) // true
Maybe.of(undefined).is(Function) // false
Maybe.Nothing().is(Object) // false

// curried examples
is(String)(Maybe.of('a string')) // true
is(Number)(Maybe.of(13)) // true
is(Number)(Maybe.of(true)) // false
is(Object)(Maybe.of({ a: 1 })) // true
is(Date)(Maybe.of(new Date())) // true
is(Object)(Maybe.Nothing()) // false

// uncurried examples
isU(String, Maybe.of('a string')) // true
isU(Number, Maybe.of(13)) // true
isU(Function, Maybe.of(x => x)), true
isU(Date, Maybe.of(new Date())), true
isU(Object, Maybe.of(new Date())), false
isU(Object, Maybe.Nothing()), false

isJust

tests whether a Maybe is of type Just

arguments: none

example:

// instance method examples
Maybe.of(99).isJust() // true
Maybe.Nothing().isJust() // false

// stand-alone examples
const { isJust } = Maybe
isJust(Maybe.of(99)) // true
isJust(Maybe.Nothing()) // false

isNothing

tests whether a Maybe is of type Nothing

arguments: none

example:

// instance method examples
Maybe.of(99).isNothing() // false
Maybe.Nothing().isNothing() // true

// stand-alone examples
const { isNothing } = Maybe
isNothing(Maybe.of(99)) // true
isNothing(Maybe.Nothing()) // false

map :: Functor Maybe

transforms the items wrapped in the Maybe instance by evaluating the map function for each item and wrapping the resulting items in another Maybe instance

arguments:

alias:

example:

const { map, mapU } = Maybe

const a = Maybe.of([ 1, 2, 3, 4 ])
const b = Maybe.of({
  a: 'Thanks',
  b: 'for', 
  c: 'all', 
  d: 'the', 
  e: 'fish' 
})
const c = Maybe.of(99)
const n = Maybe.Nothing()

// instance method examples
a.map(R.multiply(3)) // Just [ 3, 6, 9, 12 ]
b.map(R.toUpper)     // Just { 
                     //   a: 'THANKS', 
                     //   b: 'FOR', 
                     //   c: 'ALL', 
                     //   d: 'THE', 
                     //   e: 'FISH' 
                     // }
c.map(x => x % 9 === 0) // Just true
n.map(toUpper)  // Nothing

// curried examples
const tripleIt = map(R.multiply(3))
const headsUp = R.compose(
  R.prop('a'), 
  valueOr({ a: 'Sorry!' }), 
  map(R.toUpper)
)
tripleIt(a) // Just [ 3, 6, 9, 12 ]
headsUp(b) // 'THANKS'
headsUp(n) // 'Sorry!'

// uncurried examples
mapU(R.multiply(3), a) // Just [ 3, 6, 9, 12 ]
mapU(x => x % 9 === 0, c) // Just true
mapU(R.toUpper, n) // Nothing

of :: Maybe a ~> b -> Maybe b

creates a new Maybe instance of type Just that contains the specified value

arguments:

alias:

toJust :: Maybe a ~> b -> Maybe b

returns a Maybe instance of type Just containing the specified value regardless of the type of the original Maybe

arguments:

example:

const j = Maybe.of('any value')
const n = Maybe.Nothing()

j.toJust('another value') // Just "another value"
n.toJust('new value') // Just "new value"

toString

returns a string representation of the Maybe instance

arguments: none

example:

const a = Maybe.Just([ 1, 2 ])
const b = Maybe.Just({ a: 1 })
const n = Maybe.Nothing()

// instance method examples
a.toString() // 'Just [1,2]'
b.toString() // 'Just {"a":1}'
n.toString() // 'Nothing'

// standalone function examples
toString(a) // 'Just [1,2]'
toString(b) // 'Just {"a":1}'
toString(n) // 'Nothing'

valueOr

either returns the value contained by the Maybe instance or returns the supplied value in the case where the Maybe instance is of type Nothing

arguments:

example:

const { valueOr, valueOrU } = Maybe

const j = Maybe.of('selected option')
const n = Maybe.Nothing()
const defaultValue = 'default option'

// instance method examples
j.valueOr(defaultValue) // 'selected option'
n.valueOr(defaultValue) // 'default option'

// curried examples
const getValue = valueOr(defaultValue)
getValue(j) // 'selected option'
getValue(n) // 'default option'

// uncurried examples
valueOrU(defaultValue, j) // 'selected option'
valueOrU(defaultValue, n) // 'default option'

zero :: Plus Maybe => Maybe a ~> () -> Maybe a

returns a Maybe instance of type Nothing regardless of the type of the original Maybe

arguments: none

alias:

example:

Maybe.of(['values']).zero() // Nothing