Moje zdjęcie
Software Craftsman's Blog by Marcin Pieciukiewicz
Java and Scala development

Sunday, September 15, 2013

Functional approach to Exception handling in Scala

Did you know that in functional programming world there is a criticism of throwing exceptions? It seems that it would break a functional programming principle that a function have to always return a value. I'm not sure that this approach is the best for all situations, but I can imagine some cases when it might be good. Especially because Scala dumped the idea of checked exceptions.

So what we could do if we would like not to throw the exception when the exceptional situation occurs? In example, when we receive a zero as denominator in division operator. In standard approach we could throw IllegalArgumentException, but what to do in a purely functional approach? We could return a Not-A-Number value or an Infinity value. But that would not be the best approach if this exceptional situation should be handled somehow. Also there is a risk that this value would be passed further in an application and that might cause a difficult to detect problem in other part of the code.

More reliable solution would be to return an info about the error. We could reuse the IllegalArgumentException, as it is an object that nicely describes the problem and, also, it can hold a stack trace, which can be useful for debugging. So let's look at the example code:
def divide(a:Double, b:Double) = {
  if (b == 0) {
    new IllegalArgumentException("Cannot divide by zero!")
  } else {
    a / b
  }
}
That might work, but what type returns the divide function? In this case Scala compiler we'll choose Any as closest common ancestor. That means, a user of our function we'll have to check returned type and manually cast a result to a Double. You can imagine that this is inconvenient, and sometimes might cause ClassCastException when user won't predict correctly all possible types that might be returned.

Some literature suggests usage of Either[A,B] class from standard Scala's library. Basically this is an extension of an Option idea. Either object can hold a value in one of it's two fields: left and right. This can be leveraged to be useful in exception handling if we assume that we'll always put correct result in right field and error in left field. This way we could have more control over returned type, and we could handle an exception using pattern matching. I would recommend to get familiar with Either class as it can be useful in some situations, but I also think that it is not a best solution to handle exceptional situations. First, for my taste, it is too general and it requires a convention that must be known by every user. I would like to propose a different approach, inspired by Either class, but with more specific purpose.

My solution is based on a new Result class which would be a holder of a function result value, or an exception, if required. Here is it's definition:
abstract class Result[A, E <: Throwable] {
  def isSuccess: Boolean
  def isFailure: Boolean
  def get: A
  def getOrDefault(default: => A): A
  def error: E
}
As you can imagine this class can have two basic states implemented as two case classes, Success and Failure:
final case class Success[A, E <: Throwable](result: A) extends Result[A, E] {
  override def isSuccess = true
  override def isFailure = false
  override def get: A = result
  override def error: E = throw new IllegalAccessException("It's a success, not a failure")
  override def getOrDefault(default: => A): A = result
}

final case class Failure[A, E <: Throwable](err: E) extends Result[A, E] {
  override def isSuccess = false
  override def isFailure = true
  override def get: A = throw err
  override def error: E = err
  override def getOrDefault(default: => A): A = default
}
Those two are very simple implementation of abstract Result[A, E] class. Before we go into details of this implementation let's look how it can be used in our divide function:
class DivideByZeroException extends IllegalArgumentException("Cannot divide by zero")

def divide(a: Double, b: Double): Result[Double, DivideByZeroException] = {
  if (b == 0) {
    Failure(new DivideByZeroException)
  } else {
    Success(a / b)
  }
}
I hope it doesn't require a lot of explanation. Our modified function is returning a Result[Double, DivideByZeroException] type which will be a Failure containing an exception if b is equal zero, or a Success containing the result of computation in other cases.
Let's look closer on a methods provided by Result class:
  • isSuccess and isFailure - those methods simply allow us to distinguish between Success and Failure objects
  • get - this methods returns a result value in the case of a Success object, or it throws the inner exception object in the case of an Failure object
  • error - this method returns an exception object that was contained inside Failure object, or it throws an IllegalAccessException in the case of a Success instance
  • getOrDefault(default: => A) - this method is similar to get method, so it returns a result value in the case of Success object, but in the case of Failure instead of throwing an exception it returns a value passed as default (if default is an expression it is evaluated only if needed)
This set of methods, and the fact that Success and Failure are case classes, provides us with a convenient way of handle results returned from functions. Let's look at some examples where function succeedes:
scala> divide(10, 2)
res = Success(5.0)

scala> divide(10, 2).get
res = 5.0

scala> divide(10, 2).isSuccess
res = true

scala> divide(10, 2).isFailure
res = false

scala> divide(10, 2).getOrDefault(27)
res = 5.0

scala> divide(10, 2).error
java.lang.IllegalAccessException: It's a success, not a failure  // IllegalAccessException was thrown
And now the case when function returns a Failure:
scala> divide(10, 0)
res = Failure(java.lang.IllegalArgumentException: Cannot divide by zero)

scala> divide(10, 0).get
DivideByZeroException: Cannot divide by zero  // DivideByZeroException was thrown

scala> divide(10, 0).isSuccess
res = false

scala> divide(10, 0).isFailure
res = true

scala> divide(10, 0).getOrDefault(27)
res = 27.0

scala> divide(10, 0).error
res = DivideByZeroException: Cannot divide by zero  // DivideByZeroException was returned, not thrown
Those were simple, method's use cases, here you can look how it is possible to handle exceptions now:
Simple if checking for success:
val divideResult = divide(10, 2)
if (divideResult.isSuccess) {
  println("Result of division is " + divideResult.get)
} else {
  println("You shouldn't divide by zero")
}
Do you remember the C times when negative number have meant an error?
val result = divide(10, 0).getOrDefault(-1)
And this is probably most convenient way of doing this, with a usage of pattern matching:
divide(20, 5) match {
  case Success(x) => println("Result of division is " + x)
  case Failure(err) => println("Something went wrong: " + err.getMessage)
}

Conclusion
This approach to exception handling is only a proposition how it could be done if we would like to limit the throwing of the exceptions. There are people that believe in clean functional approach to programming and I think that they should be listened and understood. For me, dilemma between throwing an exception or returning it reminds me discussion about checked vs unchecked exceptions, that still doesn't have a winner.

I think that presented approach could be used when an exceptional situation is part of a business, ie. when parsing user's input. In this case we have to provide a response for any data user provided, even this corrupted. On the other hand this approach shouldn't be used to handle exception caused by internal error in our software, ie. parsing a rest request from other part of our system. In this case there is probably nothing smart to do, other than to fail gracefully, so it is best to throw an exception that will be handled by our application server.

I'm sure that with proper care both approaches have the justification for existence, even in one application.

Edit
As Konrad mentioned, Scala 2.10 is equipped with scala.util.Try class, which works almost the same as my Result class. That's great news, you can give it a try with no cost o creating custom class, and if you would like, you can use it in production code already.

4 comments:

  1. The idea is nice, and rather known - but I'd vote against the implementation shown in this example. There are minor things missing here that would make this roll-your-own impl more usable: use `sealed`, support `map` etc.

    But instead of doing an roll-your-own I'd rather encourage using Scala's standard library. Before 2.10 we've had `Either[FailureType, SuccessType]`, which implements the same idea, though less readable, as `Left(ex)` is the failure case only by convention, not really by names of the types.

    In Scala 2.10.x which has been around for a while now, we have a dedicated class implementing exactly this pattern: http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.util.Try
    Which is basically like `Either[A, B]`, but is directly aimed at this pattern, so we `Try[Something]` and can `Failure` or `Success` on it. It also composes quite beautifully - using `map`s and `flatMap`s like you'd expect any container type in Scala. It's also worth pointing out that Try takes care of catching only "non fatal" throwables (don't catch OOM etc) - a nice trick that can also be seen in Akka :-)

    Having that said, the pattern is nice and I really like it. We've used it in my previous project and current quite often too, in order to make failure explicit :-)

    ReplyDelete
  2. Hi Konrad, thanks for the info. I didn't know that there is a Try class in Scala 2.10. It seems that it works almost the same as my Result class. That's nice because it will be easier to use if it is in standard Scala's library.

    And a little explanation from myself, I've wanted to keep code as simple as possible, so it can fit in the article and be understandable by people with little Scala experience. But your points are very good, production version should be sealed and support map (and some other Scala features).

    ReplyDelete
  3. Custom things < standard library. Library things people can learns from blogs and documentation or googling. Less so custom code, so I don't think it's "simpler" to roll-your-own :-)

    ReplyDelete
  4. In most cases that's true. I've seen some projects suffering from the home made frameworks, when there were solutions on the market already. And in this case I would rather use Try instead of my solution. But I also see some places to improvement in Try, ie. declaration of exception type, or possibility to automatically log exception when it's being thrown. And this is the result of thinking about my home made Result.

    ReplyDelete