ScalaZ Disjunctions

2016-07-06

Last time, while discussing the Scala ecosystem, I said that ScalaZ often provides great alternatives to the standard library classes. In this post, we will look at ScalaZ disjunctions, which offer an alternative to Either and Try.

Indicating possible dangers

Since Scala doesn't have the notion of checked exceptions, you don't have to use the throws keyword to declare them. In Java terms, all exceptions in Scala are considered runtime and can be thrown from anywhere. Let's look at a concrete example. Here is a fictitious method (shamelessly stolen from my "Modern Web Development with Scala") that works roughly 60% of the time.

object DangerousService {
  def queryNextNumber: Long = {
    val source = Math.round(Math.random * 100)
    if (source <= 60) source
    else throw new Exception("The generated number is too big!")
  }
}

The problem here is that the callers have no idea that an exception may occur and quite possible they will be using this method until something bad happens. A better approach is to use types to communicate about possible dangers and there are several candidates that can do the job:

  • scala.Option
  • scala.util.Either
  • scala.util.Try
  • scalaz.\/

Different choices bring different limitations, so let's discuss them in detail.

Standard library options explained

Using Options is trivial. Simply change the return type from Long to Option[Long] and instead of throwing an exception, return None:

def queryNextNumber: Option[Long] = {
  val source = Math.round(Math.random * 100)
  if (source <= 60) Some(source)
  else None
}

The problem here is also quite obvious. The None value only says that there is no value, and therefore it's impossible to get to the cause.

The Either type allows us to store the exception and therefore inspect it later if something goes wrong:

def queryNextNumber: Either[Exception, Long] = {
  val source = Math.round(Math.random * 100)
  if (source <= 60) Right(source)
  else Left(new Exception("The generated number is too big!"))
}

Later, we can pattern match the value of type Either to determine whether we have a successfully calculated value or an error. The problem here is that Either doesn't really have any bias. In the example above, we used Right as a value storage and Left as an exception storage, but it is only a convention. The Either itself doesn't have map/flatMap methods, so in order to use it in for comprehensions, we would need to switch to Either projections and it is not as convenient as it should be. For details, check out Daniel Westheide's excellent post about The Either Type.

The Try type, which was added in Scala 2.10, solves above mentioned problems, but also introduces one serious limitation. Unlike Either, its left type is fixed as Throwable. Therefore, you cannot create your own error type and use it as a method result in Try.

Introducing ScalaZ disjunctions

Interestingly, ScalaZ offers an alternative to scala.util.Either which is right-biased, works great in for comprehensions and comes with some additional utilities.

The usual way to start working with ScalaZ is to import all its definitions with the following:

import scalaz._, Scalaz._

Then, you can use ScalaZ disjunctions in a way similar to scala.util.Either:

def queryNextNumber: Exception \/ Long = {
  val source = Math.round(Math.random * 100)
  if (source <= 60) \/.right(source)
  else \/.left(new Exception("The generated number is too big!"))
}

Alternatively, you can use \/[Exception, Long] instead of Exception \/ Long. Also, \/.right is the same as \/- and \/.left is the same as -\/.

Unlike scala.util.Either, Scalaz disjunctions are right biased, so you can use them easily in for comprehensions.

Replacing scala.util.Try

The Try type has a convenient feature of safely absorbing thrown exceptions. Not surprisingly, something similar is possible with disjunctions:

def queryNextNumber: Throwable \/ Long = \/.fromTryCatchNonFatal {
  val source = Math.round(Math.random * 100)
  if (source <= 60) source
  else throw new Exception("The generated number is too big!")
}

The fromTryCatchNonFatal method will happily catch all non-fatal exceptions and put them into an instance of \/. Note that here we changed our signature from Exception \/ Long to Throwable \/ Long and basically ended up with a more verbose version of Try. In reality, however, disjunctions are more flexible than that.

Let's create our own Exception subclass that will be able to store a generated number in addition to an error message:

class GenerationException(number: Long, message: String) extends Exception(message)

Instead of fromTryCatchNonFatal, we need to use the fromTryCatchThrowable method. It works in a similar way, but since it returns a custom Throwable subtype, it also requires that a NonNothing implicit value is defined in the scope:

implicit val geNotNothing = NotNothing.isNotNothing[GenerationException]

def queryNextNumber: GenerationException \/ Long = \/.fromTryCatchThrowable {
  val source = Math.round(Math.random * 100)
  if (source <= 60) source
  else throw new GenerationException(source, "The generated number is too big!")
}

If you try invoking the queryNextNumber method several times, you will see that it actually works as expected:

scala> DangerousService.queryNextNumber
res2: scalaz.\/[services.GenerationException,Long] = \/-(9)

scala> DangerousService.queryNextNumber
res3: scalaz.\/[services.GenerationException,Long] = \
  -\/(services.GenerationException: The generated number is too big!)

Sequencing disjunctions

Sometimes you end up with a collection of disjunctions:

val lst = List(queryNextNumber, queryNextNumber, queryNextNumber)

If this is the case, you may want to get the disjunction of a collection. In order to do that, simply use the sequenceU method, which was added to List via the first import:

// import scalaz._, Scalaz._

val lstD = lst.sequenceU

If all numbers were generated successfully, you will get a \/- containing a List[Long]. If there was an exception, you will get a -\/ with a GenerationException.

Conclusion

As I already stated in this post, the Scala ecosystem is extremely rich and diverse. As developers get more familiar and confident with the language, they tend to gravitate towards more powerful constructs. For example, when I was starting out, I initially settled on using Try and Future, but after some time ended up using ScalaZ disjunctions in most projects. If you're interested in advanced Scala topics or ScalaZ in particular, take a look at my book - "Mastering Advanced Scala".

Finally, if you are only starting with Scala and want to make the learning process very efficient, take a look at one of my previous posts about learning Scala by using frameworks.