Let's say I'm using a Java library from my Scala project. That Java library throws exceptions all over the place, but I don't feel comfortable just letting them propagate "in the Scala world", since there's no way to be sure what exceptions a Scala method can throw (except by documenting them). So this is the kind of code I tend to write:
def doesNotThrowExceptions(parameter: String): Either[Throwable, T] = catching(classOf[IOException], classOf[NoSuchAlgorithmException]) either { // Code calling the Java library // Code generating a value of type T }
Then, typically, I'll use Either.RightProjection.flatMap
to chain methods that return Either[Throwable, ...]
or Either.RightProjection.map
to mix methods that return Either[Throwable, ...]
and other methods. Or simply Either.fold
to do something with the Throwable
value. Somehow, though, this still doesn't feel totally right.
Is this the most "idiomatic" way to deal with Java's exceptions in Scala? Isn't there a better way?
try/catch/finally A basic way we can handle exceptions in Scala is the try/catch/finally construct, really similar to the Java one. In the following example, to make testing easier, we'll return a different negative error code for each exception caught: def tryCatch(a: Int, b: Int): Int = { try { return Calculator.
The try-catch is the simplest method of handling exceptions. Put the code you want to run in the try block, and any Java exceptions that the code throws are caught by one or more catch blocks. This method will catch any type of Java exceptions that get thrown. This is the simplest mechanism for handling exceptions.
However, unlike Java, Scala has no “checked” exceptions—you never have to declare that a function or method might throw an exception. In Java, “checked” exceptions are checked at compile time. If your method might throw an IOException, you must declare it.
I'm not sure there's a single most idiomatic way to deal with Java's exceptions because there are at least four different cases they'll get thrown:
Best practices arguably differ for each of these cases
Scala has fully-featured exception handling. There is nothing wrong with letting an unanticipated exception propagate uncaught until you're at a level where you can do something about it. Packaging every possible exception into an Either
can waste a lot of time. Just document what you know you're not handling, and use try/catch at an appropriately high level (e.g. a saveEverything
method should probably go in a try/catch block (or wrap its contents in one) because no matter what went wrong, if saving everything failed you probably want to attempt to salvage the situation, not just die).
In particular, you probably want to handle Error
this way and only package Exception
, not all Throwable
s, into Either
.
This is the case you're talking about, and you've already given several good suggestions for how to deal with them. As you've already noticed, you can use catching
to package the exceptions into Either
. You then can also
a. Use pattern matching, which will let you pull apart your Either
in more depth:
doesNotThrowExceptions("par").right.map(transformData) match { case Left(ioe: IOException) => /* ... */ case Left(nsae: NoSuchAlgorithmException) => /* ... */ case Right(x) => /* ... */ case Left(e) => throw e // Didn't expect this one... }
b. Downconvert to Option
after logging the error:
doesNotThrowExceptions("par").left.map{ e => println("You're not going to like this, but something bad happened:") println(e) println("Let's see if we can still make this work....") }.right.toOption
c. If exceptions are a really important part of your flow-control, Either
may not be enough. You might instead want to define your own Either
-like class with more than just Left
and Right
. Or you can nest the left side of the Either
:
try { Right(/* java code */) } catch { case ioe: IOException => Left(Left(ioe)) case nsae: NoSuchAlgorithmException => Left(Right(nsae)) }
d. Use Scalaz Validation
, which is a lot like Either
but is tailored a little more to exception handling.
Even though these are conceptually two different categories, you handle them the same way:
catching(classOf[IOException], classOf[NoSuchAlgorithmException]) opt { ... }
to get an Option
back. Then map
, flatMap
, etc..
I always prefer scalaz.Validation
over scala.Either
. The two are algebraically identical structures, but the former is functionally richer (has more useful methods and type class instances).
Check this link for a great presentation by Chris Marshall on scalaz.Validation
and other Scalaz goodies.
In order to talk to the code that uses classic exception handling mechanism, I have defined the following enriching methods and a type class instance:
implicit def catchW[A](underlying: Catch[A]) = new CatchW(underlying) class CatchW[A](underlying: Catch[A]) { def validation(body: => A): Validation[Throwable, A] = { underlying.withApply(_.fail)(body.success) } def vnel(body: => A): ValidationNEL[Throwable, A] = { underlying.withApply(_.failNel)(body.success) } } implicit def catchMonoid[A] = new Monoid[Catch[A]] { val zero = noCatch def append(x: Catch[A], y: => Catch[A]) = x or y }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With