# Handling Errors Without Exceptions
# The Good and Bad of Exceptions
# Referential Transparency
Exceptions break referential transparency (RT). RT expressions can be replaced with the value that they evaluate to while maintaining program meaning.
def failingFn(i: Int): Int = {
val y: Int = throw new Exception("fail!")
try {
val x = 42 + 5
x + y
} catch { case e: Exception => 43 }
}
The above function throws a runtime error when run. However if we replace y
with the expression, i.e. the throw new Exception
call, the program would return 43
. Since the program behaves differently, RT is not preserved.
# Type Safety
The type of the function (e.g. Int => Int
above) tells us nothing about the fact that exceptions may occur.
- Java has checked exceptions which kind of gets around this, but generic functions such as
map
orfoldLeft
would somehow have to be aware of exceptions as well.
# Option
The Option
data type is a solution to this by explicitly suggesting that a function will not always have a result for all inputs.
sealed trait Option[+A]
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]
The Option
data type is:
- A
Some
if it contains a value, or - A
None
if it is empty (i.e. throws an error)
# Lifting Option
When we start using options, it might seem that we have to update our caller's functions to be able to operate on options.
This can be automated through the lift
function:
def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f
# Either
With the Option
data type, we retain no information when an error occurs (we only get back None
). The Either
data type allows us to pass information for when exceptions occur.
The Either
type is either a Left
or a Right
, with the Left
type reserved for exceptional cases.
sealed trait Either[+E, +A]
case class Left[+E](get: E) extends Either[E, Nothing]
case class Right[+A](get: A) extends Either[Nothing, A]