If you have several values which might be null
, and you want the first ‘truthy’ value, one way is to use a sequence of ORs:
const c = a || b || 0
The trouble is, while this style is concise, it only works if the only values you care about are ‘truthy’.
false
, null
, undefined
, 0
, NaN
and ""
all evaluate as false. This is a problem for our example, because 0
is a valid value for c
, but if a
is 0
, we still fallback to b
:
const a = 0
const b = 1
const c = a || b || 0
//=> c === 1
So a more correct version might be:
const c = Number.isInteger(a)? a :
(Number.isInteger(b)? b : 0)
A downside to this is that code working with a
and b
has to know how to test whether a
and b
are values we can use in this context. This test logic may have to be duplicated, leaking into other parts of the code.
Maybe
lets us push this logic upstream into the functions accessing the values in the first place - returning Just(Integer)
or Nothing
instead of Integer
or null
(or undefined
or …). We already know how to fallback from a Maybe to a default value with .getOrElse(defaultValue)
. But how can we fall back from one Maybe to another?
const a = Nothing()
const b = Just(1)
const c = a.getOrElse(b.getOrElse(0))
This is one way, but the nesting, like all nesting, is a little painful, and will get more painful as we increase the number of Maybes we need to fallback through.
A nicer way is to use the alt algebra
from Fantasy Land. We can define it for Maybe very simply:
const Just = x => ({
alt: A => Just(x)
// also define map, chain, getOrElse ...
})
const Nothing = () => ({
alt: A => A
// also define map, chain, getOrElse ...
})
or, if Maybe is defined in a prototype style:
Just.prototype.alt = function(A){ return this }
Nothing.prototype.alt = function(A){ return A }
In other words, Just(something).alt(A)
always returns Just(something)
and Nothing().alt(A)
always returns A
.
This lets us write
const c = a.alt(b).getOrElse(0)
and fallback through many Maybes without nesting:
const z = y.alt(x).alt(w).alt(v).getOrElse(42)
(At the time of writing, Sanctuary is the only library to implement Maybe.alt
- but it may be about to move to fantasyland/fantasy-maybes)
You can also imagine implementing it for Task
:
const Task = fork => ({
alt: A => Task(function(reject, resolve){
fork(
err => { A.fork(reject, resolve) },
success => resolve(success)
)
}),
fork
//define map, chain, ap...
})
Task.of = x => Task((_, resolve) => resolve(x))
Used like:
writeToA
.alt(writeToB)
.alt(writeToC)
.fork(
err => console.error("failed to write to A, B & C", err),
success => console.log("Wrote successfully to one of A, B or C", success)
)
Slightly trickier is alternating between a Task Maybe
composition. eg, you want to perform a lookup on one server, and if you don’t find a result, look on a second server.
const doubleAlt = A => B => A.chain(a => {
const empty = a.constructor.empty()
return a.alt(empty).equals(empty)? B : A.constructor.of(a)
}).alt(B)
const A = Task.of(Nothing())
const B = Task.of(Just(42))
doubleAlt(A, B)
.map(m=>m.getOrElse(0))
.fork(
console.error
, console.info
) // => 42
How doubleAlt
works is, we use chain
to look inside the first Task, and if the inner Maybe is equal to an empty Maybe (ie, a Nothing
), we return the second Task. Otherwise, we return the first Task. In addition, we use .alt
on the outer Task to catch the first Task reject
ing.
In my previous post introducing Maybe, I ended with an example using Promises, mentioning that Task can be a useful alternative to javascript Promises. This was the example: getMaybeUserName() .map...
Written by Keith Alexander. Interests include functional programming, web tech, event sourcing and linked data.