Unwrap Values from a Maybe
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 map
s 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.
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.