不久之前,我开始使用Scala而不是Java。对我来说,在这些语言之间进行“转换”过程的一部分是学习使用Either
s而不是(checked)Exception
s。我已经用这种方式编码了一段时间,但是最近我开始怀疑这是否真的是更好的方法。
一个主要的优点Either
拥有Exception
更好的性能; 一个Exception
需要建立一个堆栈跟踪和被抛出。据我了解,虽然Exception
不是必须的,但构建堆栈跟踪是必需的。
但随后,人们总是可以构建/继承Exception
s的scala.util.control.NoStackTrace
,更是这样,我看到很多的情况下,其中的左侧Either
实际上是一个Exception
(免收性能提升)。
另一个优点Either
是编译器安全。Scala编译器不会抱怨未处理Exception
的(不同于Java的编译器)。但是,如果我没记错的话,那么这个决定就是用与本主题中讨论的相同的推理来推理的,所以...
在语法方面,我觉得Exception
-style更清晰。检查以下代码块(均实现相同的功能):
Either
样式:
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
样式:
@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 = ???
后者对我来说看起来要干净得多,在两种情况下,处理故障的代码(模式匹配Either
或try-catch
)都很清楚。
所以我的问题是-为什么要使用Either
over(选中)Exception
?
更新资料
阅读答案后,我意识到我可能未能展示我困境的核心。我的担心不是没有缺乏try-catch
; 可以“捕获”一个Exception
with Try
,也可以使用catch
将该异常包装为Left
。
我的主要问题是Either
/ Try
我编写的代码可能会在很多时候失败。在这些情况下,遇到故障时,我必须在整个代码中传播该故障,从而使代码变得更加繁琐(如上述示例所示)。
实际上,还有另一种不Exception
使用来破坏代码而不用s的方式return
(实际上是Scala中的另一个“ taboo”)。该代码仍比该Either
方法更清晰,并且虽然不如该Exception
样式简洁,但不必担心会被捕获Exception
。
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))
}
}
基本上,return Left
replaces throw new Exception
和任一方法上的隐式方法rightOrReturn
是对自动异常在堆栈中向上传播的补充。
Try
。关于Either
vs 的部分Exception
仅声明Either
当方法的另一种情况是“非例外”时应使用s。首先,这是一个非常非常模糊的定义恕我直言。其次,这真的值得语法惩罚吗?我的意思是,Either
如果不是因为s带来的语法开销,我真的不介意使用s。
Either
它本身不是单子。左侧或右侧的投影是单子,但Either
本身不是。您可以通过将其“偏置”到左侧或右侧来使其成为monad。但是,然后您在的两侧赋予了一定的语义Either
。Scala Either
最初是没有偏见的,但是最近才有偏见,因此,如今,它实际上已经是一元论了,但是“ monadness”不是其固有的特性,Either
而是其有偏见的结果。