Scala - From Monads to Monad Transformers by Example

March 12, 2016
Scala Monad Transformers

Scalaz’s \/ (“Disjunction” or “Either”) and Scala’s Future are great examples of how the monad abstraction can be used to simplify logic. In this post we will be looking at why we need to compose monads, how to do it (hint: with Monad Transformers), and how they can significnatly simplify our code.

The post is targetted at Scala beginners like myself who has just started to dabble with the functional side of Scala. I will be focusing more on practical examples to show how to use monad transformers. I will be using Scalaz’s \/ and Scala’s Future (scala.concurrent.Future)

Note that Scala’s standard library has an Either type as well but if you see mentions of “Either”, I am talking about Scalaz’s \/

Backstory - Parcel Mega Corp

Parcel Mega Corp is a company that makes parcels. Each Parcel needs to have a name, address and mobileNumber field, for obvious reasons.

case class User(userId: String, name: String)
case class Parcel(name: String, address: String, mobileNumber: String)

Scalaz Disjunction (\/)

With \/, you can model shortcircuiting when an error happens during your chain of operations, without the need for explicit error checks.

def makeParcel(userId: String): \/[SendParcelError, Parcel] = {
    for {
      user <- getUser(userId)
      address <- getAddress(user)
      mobileNumber <- getMobileNumber(user)
    } yield Parcel(user.name, address, mobileNumber)
}

In the above example, getUser, getAddress and getMobileNumber have the type \/[SendParcelError, X], but the code doesn’t need to explicitly check the result of each call because the monad bind (flatmap) for \/ handles them for us. If getUser fails with a UserNotFound error, the whole result of makeParcel function will be -\/(UserNotFound)

Scala Future

With Future, you can write your asynchronous code in a synchronous manner, though the code actually runs in a non-blocking, asynchronous manner.

Let’s pretend for a bit that our previous code example is fetching user/address/mobile number information from remote servers, and it cannot fail (barring unexpected exceptions which is handled upstream). You can have some code that looks almost the same. (I have put some type annotations in so you won’t be confused)

def makeParcel(userId: String): Future[Parcel] = {
  for {
    user <- getUser(userId): Future[User]
    address <- getAddress(user): Future[String]
    mobileNumber <- getMobileNumber(user): Future[String]
  } yield Parcel(user.name, address, mobileNumber)
}

In words, the above logic is

  1. Fetch the User from a remote server
  2. When the result comes back, look up the address of the User on another remote server
  3. When we obtain the address from the remote server, look up the mobileNumber of the User in another remote server

Future actually has a failiure mode as well (Future.failiure(cause: Throwable)) which causes the whole for comprehension to shortcircuit, but the type Throwable should indicate to you that we should be using this for exceptional cases (e.g. Unhandled exceptions).

The two code snippets from using Future and \/ shows you how monads are a great way to model chaining contexts.

Combining Future and \/

That’s cool and all, but what if I need to query a remote server and the user may or may not exist? The return type of my functions will look like Future[\/[SendParcelError, X]].

Let’s see what that looks like!

def makeParcelMegaCorp(userId: String): Future[\/[SendParcelError, Parcel]] = {
  import ParcelMakerMegaCorp._
  for {
    userOrError: \/[SendParcelError, User] <- remote_getUser(userId)
    addressOrError: \/[SendParcelError, String] <- userOrError match {
      case \/-(user) => remote_getAddress(user)
      case -\/(error) => Future.successful(error.left)
    }
    mobileNumberOrError: \/[SendParcelError, String] <- userOrError match {
      case \/-(user) => remote_getMobileNumber(user)
      case -\/(error) => Future.successful(error.left)
    }
  } yield for {
    user <- userOrError
    address <- addressOrError
    mobileNumber <- mobileNumberOrError
  } yield  Parcel(user.name, address, mobileNumber)
}

Ouch! That’s pretty convoluted! Let’s look at the our logic

  1. Fetch User from the remote server asynchronously
  2. Fetch address from the remote server asynchronously IF the User exists
  3. Fetch mobileNumber from the remote server asynchronously IF the both the User and their address is found

In our code example above, we need to manually implement the shortcircuit logic with something like case -\/(error) => Future.successful(error.left). This is because we couldn’t really compose the Future and \/ monad together by nesting them – We are still only working with the Future monad.

The EitherT Monad

Let me introduce a little magic type called EitherT and wrap every Future[\/[SendParcelError, Parcel]] we have with EitherT.apply:

def makeParcelMegaCorp_improved(userId: String): Future[\/[SendParcelError, Parcel]] = {
  import ParcelMakerMegaCorp._
  val logicChain = for {
    user: User <- EitherT(remote_getUser(userId))
    address: String <- EitherT(remote_getAddress(user))
    mobileNumber: String <- EitherT(remote_getAddress(user))
  } yield Parcel(user.name, address, mobileNumber)
  logicChain.run
}

It compiles and the code looks much cleaner! EitherT successfully composed our Future and \/ into so now the result we “pull out” of our composed monad is just the right(\/-) value from an asynchoronous operation.

Let’s look at the behaviour of our combined monad more carefully:

  1. When the Future completes, check that it has not failed (i.e. no exception happened)
  2. Check that the \/ is the \/- (“right”) side

Here’s an ASCII diagram showing this logic

         Future              \/
         --------       -----------
                        \/-(result)   ---->  result (continue with the for comprehension with this value)
                      /   
                     /
         Success ---
       /             \
      /               \ 
    -                   -\/(error)    ---->  shortcircuits with a Future.successful(-\/(error))
      \
       \
         Failure                      ---->  shortcircuits with a Future.failed(Throwable)

Cool, but how does this actually work?!

the logicChain variable from our code snippet above is of the type EitherT[Future, SendParcelError, Parcel]. Weird.. Let’s dig deeper!

The definition of EitherT tells us that it is just a newtype wrapper around its underlying combined monads:

final case class EitherT[F[_], A, B](run: F[A \/ B])

// Substituting in Future as F, and renaming "run" into
// "underlyingMonads" for clarity
final case class EitherT[Future, A, B](underlyingMonads: Future[A \/ B])

To make it more clear what EitherT is doing, let’s look at the map method:

def map[C](f: B => C)(implicit F: Functor[F]): EitherT[F, A, C] =
  EitherT(F.map(run)(_.map(f)))

/* Substitute Future in, keeping in mind that run == underlyingMonads == Future[A \/ B] */
def map[C](f: B => C): EitherT[Future, A, C] = 
  EitherT(
    underlyingMonads.map { either: \/[A, B] => 
      either.map(f)
    }
  )

So essentially what map method of EitherT does is first calls map on the future to get the \/ result, then calling map on it again and transform the right side of the \/ using function f. Finally it wraps the future back into the EitherT newtype wrapper.

It does similar things for methods like flatMap, filter, withFilter, which is what allows it to be used in Scala’s for comprehension.

The reason why EitherT (T stands for Transformers) is so useful is that it can transform any monad F:

EitherT[Option, A, B] // underlying monad is Option[A \/ B]
EitherT[List, A, B] // underlying monad is List[A \/ B]

There are other transformers like OptionT and ListT too!

ListT[Option, A] // underlying monad is Option[List[A]]

More importantly, since a Monad Transformer is also a monad itself, you can stack up monad transformers. Although doing this in Scala is a bit more painful than Haskell when you have monads like \/ which has more than one type parameter.

Extra Tips

Often you will find yourself having some operations that does not have the same type as your transformer. In this case, you will need to find a way to “lift” your value into your transformer type.

In our example, checking that the User has an address may not require a remote server call if the User class contains their address as one of the fields. In this case the function that does the checking will have a signature like this:

def checkUserHasAddress(user: User): \/[SendParcelError, Unit]

In this case, EitherT.fromEither will ‘inject’ your pure \/ into a Future

for {
  // ...
  user <- // ...
  _    <- EitherT.fromEither(checkUserHasAddress(user))
  // ...
}

For cases where you have a function that returns Future[A], then you can use EitherT.right or EitherT.left to lift it to your monad transformer stack.

for {
  // ...
  address <- // ...
  // getPostCode has the type Future[A]
  postCode <- EitherT.right(getPostCode(address))
  // EitherT.right takes a Future[A] and turns it into a EitherT[Future, Nothing, A]
}

It is highly beneficial to read through Scalaz’s documentation (better, the source code) and see what helper functions are available. In general, whatever you can do with a \/, there is an equivalent for EitherT. For other transformers such as OptionT and ListT there will be various helper functions too.

Final Words

We looked at how monads transformers can be useful for composing two monads together. At work I find myself using a lot of EitherT[Future, A, B] to model asynchronous operations that can fail, and chaining them together is a breeze, abstracting over the fact that each operation is asynchronous and can fail.

Also at this point in time I am looking to gradually migrate from Scala’s built-in Future to Scalaz’s Task, which from my various research seems to yield better performance and safety. (It is very similar to Haskell’s IO monad, but the API is more focused on dealing with multithreading and asynchronicity)

The source code for this example can be found Here (Using Scalaz version 7.2. If your project is using an earlier version you may need to add scalaz-contrib to your dependencies to get the Monad instance of Scala’s Future with import scalaz.contrib.std.scalaFuture._)

If you find any errors or think I can improve my post in certain ways, please send me an email! I would love to keep this up to date.

comments powered by Disqus