Not too long ago I started using Scala instead of Java. Part of the "conversion" process between the languages for me was learning to use Either
s instead of (checked) Exception
s. I've been coding this way for a while, but recently I started wondering if that's really a better way to go.
One major advantage Either
has over Exception
is better performance; an Exception
needs to build a stack-trace and is being thrown. As far as I understand, though, throwing the Exception
isn't the demanding part, but building the stack-trace is.
But then, one can always construct/inherit Exception
s with scala.util.control.NoStackTrace
, and even more so, I see plenty of cases where the left side of an Either
is in fact an Exception
(forgoing the performance boost).
One other advantage Either
has is compiler-safety; the Scala compiler won't complain about non-handled Exception
s (unlike the Java's compiler). But if I'm not mistaken, this decision is reasoned with the same reasoning that is being discussed in this topic, so...
In terms of syntax, I feel like Exception
-style is way clearer. Examine the following code blocks (both achieving the same functionality):
Either
style:
def compute(): Either[String, Int] = {
val aEither: Either[String, String] = if (someCondition) Right("good") else Left("bad")
val bEithers: Iterable[Either[String, Int]] = someSeq.map {
item => if (someCondition(item)) Right(item.toInt) else Left("bad")
}
for {
a <- aEither.right
bs <- reduce(bEithers).right
ignore <- validate(bs).right
} yield compute(a, bs)
}
def reduce[A,B](eithers: Iterable[Either[A,B]]): Either[A, Iterable[B]] = ??? // utility code
def validate(bs: Iterable[Int]): Either[String, Unit] = if (bs.sum > 22) Left("bad") else Right()
def compute(a: String, bs: Iterable[Int]): Int = ???
Exception
style:
@throws(classOf[ComputationException])
def compute(): Int = {
val a = if (someCondition) "good" else throw new ComputationException("bad")
val bs = someSeq.map {
item => if (someCondition(item)) item.toInt else throw new ComputationException("bad")
}
if (bs.sum > 22) throw new ComputationException("bad")
compute(a, bs)
}
def compute(a: String, bs: Iterable[Int]): Int = ???
The latter looks a lot cleaner to me, and the code handling the failure (either pattern-matching on Either
or try-catch
) is pretty clear in both cases.
So my question is - why use Either
over (checked) Exception
?
Update
After reading the answers, I realized that I might have failed to present the core of my dilemma. My concern is not with the lack of the try-catch
; one can either "catch" an Exception
with Try
, or use the catch
to wrap the exception with Left
.
My main problem with Either
/Try
comes when I write code that might fail at many points along the way; in these scenarios, when encountering a failure, I have to propagate that failure throughout my entire code, thus making the code way more cumbersome (as shown in the aforementioned examples).
There is actually another way of breaking the code without Exception
s by using return
(which in fact is another "taboo" in Scala). The code would be still clearer than the Either
approach, and while being a bit less clean than the Exception
style, there would be no fear of non-caught Exception
s.
def compute(): Either[String, Int] = {
val a = if (someCondition) "good" else return Left("bad")
val bs: Iterable[Int] = someSeq.map {
item => if (someCondition(item)) item.toInt else return Left("bad")
}
if (bs.sum > 22) return Left("bad")
val c = computeC(bs).rightOrReturn(return _)
Right(computeAll(a, bs, c))
}
def computeC(bs: Iterable[Int]): Either[String, Int] = ???
def computeAll(a: String, bs: Iterable[Int], c: Int): Int = ???
implicit class ConvertEither[L, R](either: Either[L, R]) {
def rightOrReturn(f: (Left[L, R]) => R): R = either match {
case Right(r) => r
case Left(l) => f(Left(l))
}
}
Basically the return Left
replaces throw new Exception
, and the implicit method on either, rightOrReturn
, is a supplement for the automatic exception propagation up the stack.
Try
. The part aboutEither
vsException
merely states thatEither
s should be used when the other case of the method is "non-exceptional". First, this is a very, very vague definition imho. Second, is it really worth the syntax penalty? I mean, I really wouldn't mind usingEither
s if it weren't for the syntax overhead they present.Either
looks like a monad to me. Use it when you need the functional composition benefits that monads provide. Or, maybe not.Either
by itself is not a monad. The projection to either the left side or the right side is a monad, butEither
by itself isn't. You can make it a monad, by "biasing" it to either the left side or the right side, though. However, then you impart a certain semantic on the both sides of anEither
. Scala'sEither
was originally unbiased, but was biased rather recently, so that nowadays, it is in fact a monad, but the "monadness" is not an inherent property ofEither
but rather a result of it being biased.