Unwrap Values from a Maybe

4 min read

In the previous articles in this series, we've covered the Maybe type to gain a general understanding, and we've seen how we can construct a Maybe with the safe function. Applying functions to values within the context of a Maybe means we can perform our transformations with more confidence. At some point, we’re going to need to extract our value to use it in places where our code expects a raw value. In this article we'll see how to do that after we introduce a little bit of function composition.

Setup

Our starting code has some imports from the Crocks library, two small utilities called inc and dbl, and a function called incDbl that uses the inc and dbl utilities to combine them into a single function.

import safe from 'crocks/Maybe/safe'
import isNumber from 'crocks/predicates/isNumber'

export const inc = (n) => n + 1

export const dbl = (n) => n * 2

const incDbl = (n) => {
  const incremented = inc(n)
  return dbl(incremented)
}

const result = incDbl(2)
console.log(result) // 6

Using a Maybe

If we pass a string like '2' into incDbl we'll end up with 42 since inc will coerce the 1 to a string, resulting in '21' then dbl will coerce that '21' to a number and double it. Let's introduce a Maybe to avoid that sort of unintended result.

Right in the incDbl function, we can put safe and isNumber to use. That means we'll need to map over our functions instead of calling them with directly with the data since it will now be wrapped up in the Maybe type.

import safe from 'crocks/Maybe/safe'
import isNumber from 'crocks/predicates/isNumber'

export const inc = (n) => n + 1

export const dbl = (n) => n * 2

const incDbl = (n) => {
  const safeNum = safe(isNumber, n)
  return safeNum.map(inc).map(dbl)
}

const result = incDbl(2)
console.log(result.toString()) // Just 6

With that, we get back a Just 6 for the number 2 as input, and a Nothing if we pass in a non-number.

Composing The Functions

Because we're calling these two utility functions, we ended up having to map over each of them individually. By mapping multiple times, what we end up doing is unwrapping the value from the Maybe, invoking the function, wrapping the value again, then immediately unwrapping, invoking, and wrapping. Instead of mapping over two functions, let's create a function composition so we can reduce this down to one map.

We'll start by importing compose from Crocks:

import compose from 'crocks/helpers/compose'

Then we can replace the multiple maps with a single map over a composition of the inc and dbl functions.

 const incDbl = (n) => {
   const safeNum = safe(isNumber, n)
-  return safeNum.map(inc).map(dbl)
+  return safeNum.map(compose(dbl, inc))
 }

This will give us the same results, but now we only need to unwrap and rewrap the value once.

For a Deeper Dive on Function Composition
If you want to understand composition at a deeper level, I have an article called Learn Function Composition By Building compose and composeAll Utility Functions that goes into detail on that.

Unwrapping the Value

Now that we've added the safety of a Maybe and used composition to avoid unnecessary steps, we need to unwrap the value so our original incDbl function can continue to be consumed in the same way as when we started (only safer 😉).

Each Maybe instance comes with a handy method called option. The option method accepts a value to be returned in the case of a Nothing. Otherwise, option returns the raw, unwrapped version from a Just.

 const incDbl = (n) => {
  const safeNum = safe(isNumber, n)
- return safeNum.map(compose(dbl, inc))
+ return safeNum.map(compose(dbl, inc)).option(0)
 }

With that, we will get a 0 for any input that results in a Nothing and the value of calling incDbl with any valid value.