You have a function that either returns a value or throws an exception.
Change the function's return type to a disjunction of the original return type and the exception.
We use the following form for our disjunction, called
Either
:
sealed trait Either[A,B]
case class Left[A,B](x: A) extends Either[A,B]
case class Right[A,B](x: B) extends Either[A,B]
This is similar to Scala's built-in Either
type, but
with support for for
-comprehensions.
Checked exceptions force the developer to wrap their code in
try/catch
blocks, yielding noisy code that's hard to reason
about, hard to combine, and impure in languages where
try/catch
blocks are not expressions.
Unchecked exceptions allow the developer to forget to wrap their code
in try/catch
blocks, creating a runtime timebomb.
When encountering an expression that throws an exception, note the
types of the exception that it can throw, and the value that it can
return. We'll call these A
and B
,
respectively.
Remodel the expression's return type to Either[A,B]
.
Change any throw e
into Left(e)
.
Change any return b
into Right(b)
.
def div(x: Int, y: Int): Int = x / y
val quotient: Int =
try {
div(42, 0)
} catch {
case e: ArithmeticException => -999
}
println(s"quotient: ${quotient}") // quotient: -999
def divE(x: Int, y: Int): Either[Exception, Int] =
try {
Right(x / y)
} catch {
case e: Exception => Left(e)
}
val quotientE: Either[Exception,Int] = divE(42, 0)
println(s"quotientE: ${quotientE}") // quotientE: Left(java.lang.ArithmeticException: / by zero)
implicit class RightBiasedEither[A,B](e: Either[A,B]) {
def flatMap[C](f: B => Either[A,C]): Either[A,C] =
match {
e case Left(a) => Left(a)
case Right(b) => f(b)
}
def map[C](f: B => C): Either[A,C] =
{ b =>
flatMap Right(f(b))
}
}
val quotientE2: Either[Exception,Int] =
for {
<- divE(42, 7)
a <- divE(a, 0)
b } yield b
println(s"quotientE2: ${quotientE2}") // quotientE2: Left(java.lang.ArithmeticException: / by zero)
quotient: -999
quotientE: Left(java.lang.ArithmeticException: / by zero)
quotientE2: Left(java.lang.ArithmeticException: / by zero)