Skip to content


Scala exceptions vs. pattern matching

I’ve recently been working on the talking puffin project, particularly on the lower level twitter API. At the plumbing level, we want a class that allows us to fetch XML from a URL. In Java doing this sort of stuff usually involves quite a few checked exceptions. The interwebs are flakey, and ignoring that fact usually leads to really bad things happening. However Scala doesn’t have checked exceptions… and that’s a nice thing for flexibility, conciseness, etc. So what to do?

My two basic approaches were

  1. Throw exceptions on errors. this allows more concise code when you don’t care about the errors (maybe you’re dealing with them higher up)
  2. Use case classes with subclasses indicating successful and unsuccessful execution. This forces you to deal with error conditions right away, which may lead to more stable code

Let’s take a look at implementations of both. First we set up a simple base class to deal with HTTP plumbing. This just provides a way to open a URL and a helper method to read the error stream into a string.

class HttpBase{
  /**
  * Open the specified URL and return a tuple containing the corresponding
  * HTTP response code and HttpURLConnection
  */
  def openConnection(urlStr:String) = {
    val url = new URL(urlStr)
    val conn = (url.openConnection).asInstanceOf[HttpURLConnection]
    val responseCode = conn.getResponseCode()
    (responseCode,conn)
  }
 
  /**
  * Utility to open a connection's error stream and read it into a string'
  */
  def getErrMsg(conn:HttpURLConnection) = {
    var errMsg = ""
    val reader = new BufferedReader(
          new InputStreamReader(conn.getErrorStream()))
    var line = reader.readLine()
    while(line != null){
      errMsg += line
      line = reader.readLine()
    }
    errMsg
  }
}

Now let’s try to implement a class that actually fetches XML, and throws an exception if errors are encountered.

case class HttpException(val msg:String, val code:Int) extends Exception
 
/**
* Provides a getXML method that fetches XML from a URL.
* Throws an exception if any errors are encountered
*/
class HttpXMLExceptions extends HttpBase{
  def getXML(urlStr:String):Node = {
    try{
      openConnection(urlStr) match {
        case (200,conn) => XML.load(conn.getInputStream())
        case (code,conn) => throw HttpException(getErrMsg(conn),code)
      }
    }catch{
      case e => throw HttpException(e.toString,-1)
    }
  }
}

And here’s a usage sample. This is concise, but (by design) we aren’t forced to think about nasty things like 404s, 401s, dropped connections, broken pipes, and all sorts of other networky hobgoblins.

    val excs = new HttpXMLExceptions()
    val url = "http://twitter.com/statuses/public_timeline.xml"
    val content = excs.getXML(url)

Let’s try to force people to think about them. Instead of throwing an exception, we can define a hierarchy of case classes for our responses, like so.

case class Response
case class Success(val content:Node) extends Response
case class Error(val msg:String,val code:Int) extends Response

Our method will return a type of Response. If our request is successful we’ll get a Success object that has a content field. If we run into any errors we’ll instead get an Error object back with a response code and error message from the response body. This is similar to using Scala’s Option type, however this allows us to provide information on failure instead of simple returning the None instance. Here we go…

/**
* Proveds a getXML method that fetches XML from a URL.
* Always returns a Response object, regardless of success/failure
*/
class HttpXMLMatches extends HttpBase{
  def getXML(urlStr:String):Response = {
    try{
      openConnection(urlStr) match {
        case (200,conn) => Success(XML.load(conn.getInputStream()))
        case (code,conn) => Error(getErrMsg(conn),code)
      }
    }catch{
      case e => Error(e.toString,-1)
    }
  }
}

This is pleasantly similar in implementation… However usage looks quite a bit different…

    val matches = new HttpXMLMatches()
    val url = "http://twitter.com/statuses/public_timeline.xml"
    val content = matches.getXML(url) match {
      case Success(node) => node
      case Error(msg,code) => 
        <error><msg>{msg}</msg><code>{code}</code></error>
    }

This is quite a bit more verbose. However it does send a message that developers need to be conscious of the potential error case. Let’s look at side by side usage when we add error handling for an unknown host

    val excs = new HttpXMLExceptions()
    val badUrl = "http://nohost.twitter.com/statuses/public_timeline.xml"
    val noContent = try{
      excs.getXML(badUrl)
    }catch{
      case HttpException(msg,code) => 
        <error><msg>{msg}</msg><code>{code}</code></error>
    }
 
    val matches = new HttpXMLMatches()
    val noMatchContent = matches.getXML(badUrl) match {
      case Success(node) => node
      case Error(msg,code) => 
        <error><msg>{msg}</msg><code>{code}</code></error>
    }

This usage actually looks pretty similar too. This is due to the fact that a try/catch evaluates like a pattern match. In the exception case your error conditions are separated from the main logic, which some may like and some may not.

The primary difference is that using the Response class hierarchy forces the user to consider the the unsunny day scenarios. I don’t think there’s a black and white rule on when to use either approach, but case classes provide you a way to expose error conditions more visibly than unchecked exceptions. In the talking puffin project I think we’ll stick with exceptions, but it’s nice to have options.

Posted in scala.

  • chris_lewis
    There's more to monadic error handling than just forcing the developer to state via verbosity that he is aware of a potential error. Note that scala.Option defines a few higher-order functions, namely foreach, filter, map, and flatMap: http://www.scala-lang.org/docu/files/api/scala/...

    These allow us to pass behavior to an Option instance that will only execute if it has a value (ie an instance of Some). For example, to get a collection of the status updates:

    val matches = new HttpXMLMatches()
    val url = "http://twitter.com/statuses/public_timeline.xml"
    matches.getXML(url).map { _ \\ "status" \ "text" map { _ text } }

    This results in a Some[Seq[String]] of the text nodes of the status updates. You could also use for comprehensions:

    for(xml <- matches.getXML(url)) yield (xml \\ "status" \ "text" map { _ text })

    Or if you just want to do some side-effecting action, like printing the text:

    for(xml <- matches.getXML(url)) { println(xml \\ "status" \ "text" text) }

    I'm still trying to work out some guidelines as to when monadic handling is preferable to exceptions.
blog comments powered by Disqus