Error handling is one of those things that you probably don’t need to care too much when started with a programming language, but it will become super important once you want to do some serious stuff with it.
In a traditional imperative language, errors are mostly handled by a try
and catch
clause. For example, if we are reading something from DynamoDB, we can handle it using the following pattern
1 | try { |
In this way, the response is of type Option
and can be pass along to downstream logics and eventually be handled when it is used.
1 | ddbContentOption.fold { |
Try
Use Option
to pass along exceptions is easy, but also is very limited in terms of the types of exceptions being handled. A more powerful mechanism was introduced in Scala 2.10
, i.e., the Try
keyword.
Try
can be used to wrap around methods, which results in an instance Try[A]
that 1) if the computation is successful, it’s an instance of Success[A]
, simply wrapping the value of type A
; and 2) if the computation errors out, it’s an instance of Failure[A]
, wrapping a Throwable
.
Going back to our toy example, we can rewrite it as
1 | import scala.util.Try |
Working with Try
values is very similar to Option
- you can use all the typical functional sugar with it, such as getOrElse
, map
/flatMap
and for comprehensions.
Specifically to Try
, you can use isSuccess
to check if the computation is successful; or use pattern matching to handle success and failure accordingly.
1 | import scala.util.{Success, Failure} |
Moreover, you don’t have to use getOrElse
to set default values for Try
. Instead, you could take advantage of the recover
or recoverWith
methods, which returns a Success
by applying a partial function on the given Failure
instance.
Either
Alternatively, people are also using Either
for this purpose. But, similar to Option
, Either
has its usage outside of error handling.
Either
takes two type parameters A
and B
. An instance of Either[A, B]
is either an instance of A
or an instance of B
, which is defined by two sub types Left
and Right
. For example, an Either
is a Left
if it is an instance of A
.
In error handling, the convention is to use the Left
to represent the error case and Right
for the success value. Therefore, our DynamoDB example can be wrapped using Either
in this way
1 | val ddbContent: Either[String, DdbContent] = |
In downstream, we can use pattern matching to handle success or failure.
1 | ddbContent match { |
Unlike Option
or Try
, Either
is unbiased, which means you need to choose the assumption that it is a Left
or Right
by calling .left
or .right
. Then you will get a LeftProjection
or RightProjection
as a left or right biased wrapper for the Either
.
Summary
Scala provides a couple of nice APIs to work with error handling, such as Option
, Try
and Either
. It prefers these functional style handling as opposed to the more traditional try
and catch
with side effects. With a few caveats, you can work with these APIs using standard funcitonal sugars.
- As always, I would really appreciate your thoughts/comments. Feel free to leave them following this post or tweet me @_LeiG.