Left Fold

Working with Tasks containing Maybes: another empty Future

In a previous article, we looked at how to work with a Task inside a Maybe, which is useful when you only want to do some asynchronous operation if you have a value.

A variation on this is a Task containing a Maybe, which is useful for describing, for example:

  • a database query that may return an empty result (eg findById('84hjk6h'))
  • an HTTP request to a REST endpoint that might return a 404
  • reading a file that might contain text, or might be empty

The Maybe inside the Task gives us a way to only do something if the result is wrapped in Just. If the result is Nothing, we do nothing.

If we have:

const result = findById(realID)
//=> Task.of(Just({name: "Robin", img: "343592.jpg"}))

we want to transform the object to HTML, and insert it into the page.

If we have:

const result = findById(nonexistentID)

We don’t want to do anything.

The tricky thing with this structure is that if we want to transform the inner value, we have to map twice to get at it:

result.map(maybe => maybe.map(transformValue))

which would be terrible.

And if we unwrap the Maybe with .getOrElse(value) we have to provide a default value, and then we map directly over either the result if it exists, or the default if it doesn’t. But we don’t want to do anything if we have a Nothing. What we really want is to turn our outer Task into an empty Task if it contains a Nothing, and a Task containing a result if it contains a Just of a result.

We can achieve this by transforming our Maybe into a Task, and using chain with that transformation to ensure that we flatten Task(Task(result)) into Task(result):

const maybeToTask = m => m.map(Task.of).getOrElse(Task.empty())
maybeToTask(Just(42)) //=> Task(42)
maybeToTask(Nothing()) //=> Task.empty()

As we said previously, Task.empty() returns a Task that will ignore the error and success handlers you pass to .fork(err, success) and will therefore do nothing at all.

result // Task(Just(result)) or Task(Nothing())
.chain(maybeToTask) //Task(result) or Task.empty()
    renderUser //won't be called if result is Task(Nothing())

(Note, you won’t always want to ignore Nothings - sometimes you’ll want to use a default value, or do something completely different.)

Posted February 1, 2017

Fallbacks with Alt

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...

author Keith AlexanderWritten by Keith Alexander. Interests include functional programming, web tech, event sourcing and linked data.