HOME - BLOG POSTS - TYPELEVEL CATS

# MonadError - Handling failed futures functionally

- 4 minutes read - 711 wordsScala’s Either type allows us to deal with two paths of execution (Left or Right). Futures do the same (Success or Failure). Stacking Future and Either results in three execution paths (Success-Left, Success-Right, Failure) and it’s easy to forget about the third scenario. Cats’ MonadError allows us to reduce (Success-Left, Success-Right, Failure) to (Left or Right) by transforming a failed future into a Success-Left.

In my previous post I talked about Monad Transformers. I find `EitherT[Future, MyError, ?]`

to be a particularly nice way
of dealing with asynchronous operations:

```
def getUserId: Either[UserNotFound, Int] = ???
def fetchUser(id: Int): Future[Either[UserNotFound, User]] = ???
def fetchOrder(user: User): Future[Either[OrderNotFound, Order]] = ???
val eitherT = for {
userId <- EitherT.fromEither[Future](getUserId: Either[ServiceError, Int])
user <- EitherT(fetchUser(userId): Future[Either[ServiceError, User]])
order <- EitherT(fetchOrder(user): Future[Either[ServiceError, Order]])
} yield order
Await.result(eitherT.value, 1.second)
```

So far so good, at the end of the flow we will either have a ServiceError or an Order. Well actually that’s not quite true because the Futures themselves can fail. It’s easy to forget about this edge case

### Using recover / recoverWith

One option is to transform the futures themselves to ensure they never fail:

`EitherT(fetchUser(userId).recover { case t => Left(MyError(t.getMessage) })`

It works but it’s a bit messy because we need to do this for every call that returns a Future i.e. `fetchUser`

and `fetchAddress`

### MonadError

Alternatively we can handle this edge case using the Monad Transformer. Cats includes a Monad typeclass called MonadError for this scenario:

A monad that also allows you to raise and or handle an error value. This type class allows one to abstract over error-handling monads

So how do we use it. We use MonadError’s `recoverWith`

method to transform our original EitherT into a version that recovers
from the failed future:

```
val eitherT = for { ... }
val recoveredEitherT = MonadError[EitherT[Future, MyError, ?], Throwable].recoverWith(eitherT) {
case t => EitherT.leftT[Future, Address](MyError(t.getMessage))
}
```

What’s the ? you may ask? As usual it’s a compiler hack. Lets take a look at the signature for MonadError.apply: `def apply[F[_], E]`

so it expects an `F[_]`

(the underlying Monad) and an `E`

(the error/failure type - in our case Throwable). If you’ve
been following my other posts you’ll recognise that we need `F[_]`

but our EitherT is actually `F[G[_], A, B]`

. As usual
we fix two of the three types and leave one free to give us `F[_]`

. I’m using the kind compiler plugin to reduce boilerplate
by using an anonymous type. I could have achieved the same result using an explicit type:

```
type MyEitherT[A] = EitherT[Future, ServiceError, A]
val recoveredEitherT = MonadError[MyEitherT, Throwable].recoverWith(eitherT) {
case t => EitherT.leftT[Future, Order](OtherError(t.getMessage): ServiceError)
}
```

### Reuse

We can make this a bit more generic, allowing us to handle any EitherT stack:

```
implicit class RecoveringEitherT[F[_], A, B](underlying: EitherT[F, A, B])(implicit me: MonadError[F, Throwable]) {
def recoverF(op: Throwable => A) = MonadError[EitherT[F, A, ?], Throwable].recoverWith(underlying) {
case t => EitherT.fromEither[F](op(t).asLeft[B])
}
}
val eitherT = ???
val recoveredEitherT2 = eitherT.recoverF(t => OtherError(s"future failed - ${t.getMessage}"): ServiceError)
```

Let’s break this down:

Firstly I’m using an implicit class to pimp EitherT, adding a recoverF method

The

`F[_]`

type parameter says our`F`

should be a type constructor (wrapper type) e.g. Future/Monix Task/Option etc.`A`

represents the Left type and`B`

represents the Right typeThe implicit

`MonadError[F, Throwable]`

is the interesting parameter. It tells the compiler we need a MonadError instance for our F and Throwable. Cats includes such an implementation for FutureWe pass an

`op`

parameter which just says given a Throwable, generate our Left type (`A`

)Finally we use MonadError’s recoverWith to transform Throwable to A using the provided op

### Why use MonadError

As I mentioned before, we can simply recover from any future before wrapping it in an EitherT. Alternatively we can recover a failed future in the final step when we turn the EitherT back into a future:

`val eventualResult = eitherT.value.recover { case t => Left(MyError(t.getMessage)) }`

Both approaches are valid but they’re either too fine or coarse grained. We probably don’t want to write code to recover from every future. Equally a “catch all” style handler is probably not very useful