Daniel Westheide

on making software

The Neophyte's Guide to Scala Part 6: Error Handling With Try

When just playing around with a new language, you might get away with simply ignoring the fact that something might go wrong. As soon you want to create anything serious, though, you can no longer run away from handling errors and exceptions in your code. The importance of how well a language supports you in doing so is often underestimated, for some reason or another.

Scala, as it turns out, is pretty well positioned when it comes to dealing with error conditions in an elegant way. In this article, I’m going to present Scala’s approach to dealing with errors, based on the Try type, and the rationale behind it. I’m using features introduced with Scala 2.10 and ported back to Scala 2.9.3, so make sure your Scala version in SBT is at 2.9.3 or later.

Throwing and catching exceptions

Before going straight to Scala’s idiomatic approach at error handling, let’s first have a look at an approach that is more akin to how you are used to working with error conditions if you come from languages like Java or Ruby. Like these languages, Scala allows you to throw an exception:

1
2
3
4
5
6
7
case class Customer(age: Int)
class Cigarettes
case class UnderAgeException(message: String) extends Exception(message)
def buyCigarettes(customer: Customer): Cigarettes =
  if (customer.age < 16)
    throw UnderAgeException(s"Customer must be older than 16 but was ${customer.age}")
  else new Cigarettes

Thrown exceptions can be caught and dealt with very similarly to Java, albeit using a partial function to specify the exceptions we want to deal with. Also, Scala’s try/catch is an expression, so the following code returns the message of the exception:

1
2
3
4
5
6
7
val youngCustomer = Customer(15)
try {
  buyCigarettes(youngCustomer)
  "Yo, here are your cancer sticks! Happy smokin'!"
} catch {
    case UnderAgeException(msg) => msg
}

Error handling, the functional way

Now, having this kind of exception handling code all over your code base can become ugly very quickly and doesn’t really go well with functional programming. It’s also a rather bad solution for applications with a lot of concurrency. For instance, if you need to deal with an exception thrown by an Actor that is executed on some other thread, you obviously cannot do that by catching that exception – you will want a possibility to receive a message denoting the error condition.

Hence, in Scala, it’s usually preferred to signify that an error has occurred by returning an appropriate value from your function.

Don’t worry, we are not going back to C-style error handling, using error codes that we need to check for by convention. Rather, in Scala, we are using a specific type that represents computations that may result in an exception.

In this article, we are confining ourselves to the Try type that was introduced in Scala 2.10 and later backported to Scala 2.9.3. There is also a similar type, called Either, which, even after the introduction of Try, can still be very useful, but is more general.

The semantics of Try

The semantics of Try are best explained by comparing them to those of the Option type that was the topic of the previous part of this series.

Where Option[A] is a container for a value of type A that may be present or not, Try[A] represents a computation that may result in a value of type A, if it is successful, or in some Throwable if something has gone wrong. Instances of such a container type for possible errors can easily be passed around between concurrently executing parts of your application.

There are two different types of Try: If an instance of Try[A] represents a successful computation, it is an instance of Success[A], simply wrapping a value of type A. If, on the other hand, it represents a computation in which an error has occurred, it is an instance of Failure[A], wrapping a Throwable, i.e. an exception or other kind of error.

If we know that a computation may result in an error, we can simply use Try[A] as the return type of our function. This makes the possibility explicit and forces clients of our function to deal with the possibility of an error in some way.

For example, let’s assume we want to write a simple web page fetcher. The user will be able to enter the URL of the web page they want to fetch. One part of our application will be a function that parses the entered URL and creates a java.net.URL from it:

1
2
3
import scala.util.Try
import java.net.URL
def parseURL(url: String): Try[URL] = Try(new URL(url))

As you can see, we return a value of type Try[URL]. If the given url is syntactically correct, this will be a Success[URL]. If the URL constructor throws a MalformedURLException, however, it will be a Failure[URL].

To achieve this, we are using the apply factory method on the Try companion object. This method expects a by-name parameter of type A (here, URL). For our example, this means that the new URL(url) is executed inside the apply method of the Try object. Inside that method, non-fatal exceptions are caught, returning a Failure containing the respective exception.

Hence, parseURL("http://danielwestheide.com") will result in a Success[URL] containing the created URL, whereas parseURL("garbage") will result in a Failure[URL] containing a MalformedURLException.

Working with Try values

Working with Try instances is actually very similar to working with Option values, so you won’t see many surprises here.

You can check if a Try is a success by calling isSuccess on it and then conditionally retrieve the wrapped value by calling get on it. But believe me, there aren’t many situations where you will want to do that.

It’s also possible to use getOrElse to pass in a default value to be returned if the Try is a Failure:

1
val url = parseURL(Console.readLine("URL: ")) getOrElse new URL("http://duckduckgo.com")

If the URL given by the user is malformed, we use the URL of DuckDuckGo as a fallback.

Chaining operations

One of the most important characteristics of the Try type is that, like Option, it supports all the higher-order methods you know from other types of collections. As you will see in the examples to follow, this allows you to chain operations on Try values and catch any exceptions that might occur, and all that in a very readable manner.

Mapping and flat mapping

Mapping a Try[A] that is a Success[A] to a Try[B] results in a Success[B]. If it’s a Failure[A], the resulting Try[B] will be a Failure[B], on the other hand, containing the same exception as the Failure[A]:

1
2
3
4
parseURL("http://danielwestheide.com").map(_.getProtocol)
// results in Success("http")
parseURL("garbage").map(_.getProtocol)
// results in Failure(java.net.MalformedURLException: no protocol: garbage)

If you chain multiple map operations, this will result in a nested Try structure, which is usually not what you want. Consider this method that returns an input stream for a given URL:

1
2
3
4
import java.io.InputStream
def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u =>
  Try(u.openConnection()).map(conn => Try(conn.getInputStream))
}

Since the anonymous functions passed to the two map calls each return a Try, the return type is a Try[Try[Try[InputStream]]].

This is where the fact that you can flatMap a Try comes in handy. The flatMap method on a Try[A] expects to be passed a function that receives an A and returns a Try[B]. If our Try[A] instance is already a Failure[A], that failure is returned as a Failure[B], simply passing along the wrapped exception along the chain. If our Try[A] is a Success[A], flatMap unpacks the A value in it and maps it to a Try[B] by passing this value to the mapping function.

This means that we can basically create a pipeline of operations that require the values carried over in Success instances by chaining an arbitrary number of flatMap calls. Any exceptions that happen along the way are wrapped in a Failure, which means that the end result of the chain of operations is a Failure, too.

Let’s rewrite the inputStreamForURL method from the previous example, this time resorting to flatMap:

1
2
3
def inputStreamForURL(url: String): Try[InputStream] = parseURL(url).flatMap { u =>
  Try(u.openConnection()).flatMap(conn => Try(conn.getInputStream))
}

Now we get a Try[InputStream], which can be a Failure wrapping an exception from any of the stages in which one may be thrown, or a Success that directly wraps the InputStream, the final result of our chain of operations.

Filter and foreach

Of course, you can also filter a Try or call foreach on it. Both work exactly as you would except after having learned about Option.

The filter method returns a Failure if the Try on which it is called is already a Failure or if the predicate passed to it returns false (in which case the wrapped exception is a NoSuchElementException). If the Try on which it is called is a Success and the predicate returns true, that Succcess instance is returned unchanged:

1
2
3
def parseHttpURL(url: String) = parseURL(url).filter(_.getProtocol == "http")
parseHttpURL("http://apache.openmirror.de") // results in a Success[URL]
parseHttpURL("ftp://mirror.netcologne.de/apache.org") // results in a Failure[URL]

The function passed to foreach is executed only if the Try is a Success, which allows you to execute a side-effect. The function passed to foreach is executed exactly once in that case, being passed the value wrapped by the Success:

1
parseHttpURL("http://danielwestheide.com").foreach(println)

For comprehensions

The support for flatMap, map and filter means that you can also use for comprehensions in order to chain operations on Try instances. Usually, this results in more readable code. To demonstrate this, let’s implement a method that returns the content of a web page with a given URL using for comprehensions.

1
2
3
4
5
6
7
8
import scala.io.Source
def getURLContent(url: String): Try[Iterator[String]] =
  for {
    url <- parseURL(url)
    connection <- Try(url.openConnection())
    is <- Try(connection.getInputStream)
    source = Source.fromInputStream(is)
  } yield source.getLines()

There are three places where things can go wrong, all of them covered by usage of the Try type. First, the already implemented parseURL method returns a Try[URL]. Only if this is a Success[URL], we will try to open a connection and create a new input stream from it. If opening the connection and creating the input stream succeeds, we continue, finally yielding the lines of the web page. Since we effectively chain multiple flatMap calls in this for comprehension, the result type is a flat Try[Iterator[String]].

Please note that this could be simplified using Source#fromURL and that we fail to close our input stream at the end, both of which are due to my decision to keep the example focussed on getting across the subject matter at hand.

Pattern Matching

At some point in your code, you will often want to know whether a Try instance you have received as the result of some computation represents a success or not and execute different code branches depending on the result. Usually, this is where you will make use of pattern matching. This is easily possible because both Success and Failure are case classes.

We want to render the requested page if it could be retrieved, or print an error message if that was not possible:

1
2
3
4
5
6
import scala.util.Success
import scala.util.Failure
getURLContent("http://danielwestheide.com/foobar") match {
  case Success(lines) => lines.foreach(println)
  case Failure(ex) => println(s"Problem rendering URL content: ${ex.getMessage}")
}

Recovering from a Failure

If you want to establish some kind of default behaviour in the case of a Failure, you don’t have to use getOrElse. An alternative is recover, which expects a partial function and returns another Try. If recover is called on a Success instance, that instance is returned as is. Otherwise, if the partial function is defined for the given Failure instance, its result is returned as a Success.

Let’s put this to use in order to print a different message depending on the type of the wrapped exception:

1
2
3
4
5
6
7
import java.net.MalformedURLException
import java.io.FileNotFoundException
val content = getURLContent("garbage") recover {
  case e: FileNotFoundException => Iterator("Requested page does not exist")
  case e: MalformedURLException => Iterator("Please make sure to enter a valid URL")
  case _ => Iterator("An unexpected error has occurred. We are so sorry!")
}

We could now safely get the wrapped value on the Try[Iterator[String]] that we assigned to content, because we know that it must be a Success. Calling content.get.foreach(println) would result in Please make sure to enter a valid URL being printed to the console.

Conclusion

Idiomatic error handling in Scala is quite different from the paradigm known from languages like Java or Ruby. The Try type allows you to encapsulate computations that result in errors in a container and to chain operations on the computed values in a very elegant way. You can transfer what you know from working with collections and with Option values to how you deal with code that may result in errors – all in a uniform way.

To keep this article at a reasonable length, I haven’t explained all of the methods available on Try. Like Option, Try supports the orElse method. The transform and recoverWith methods are also worth having a look at, and I encourage you to do so.

In the next part we are going to deal with Either, an alternative type for representing computations that may result in errors, but with a wider scope of application that goes beyond error handling.

Comments