答案 0 :(得分:15)
关于Either
的好处是,您可以跟踪原因缺少某些内容。例如,如果您使用Options
,则可能出现以下情况:
val xOpt = Option(1)
val yOpt = Option(2)
val zOpt = None
val tupled = for {
x <- xOpt
y <- yOpt
z <- zOpt
} yield (x, y, z)
现在,如果tupled
是None
,我们真的不知道为什么!如果这是其余行为的重要细节,则使用Either
可以提供帮助:
val tupled = for {
x <- xOpt.toRight("x is missing").right
y <- yOpt.toRight("y is missing").right
z <- zOpt.toRight("z is missing").right
} yield (x, y, z)
这将返回Left(msg)
,其中消息是第一个缺失值的相应消息,或者Right(value)
表示为tupled值。传统上保持使用Left
表示失败,Right
表示成功。
当然,您也可以更广泛地使用Either
,不仅适用于缺失值或特殊值的情况。还有其他情况Either
可以帮助表达简单联合类型的语义。
用于例外值的第三个常见习语是Try
monad:
val xTry = Try("1".toInt)
val yTry = Try("2".toInt)
val zTry = Try("asdf".toInt)
val tupled = for {
x <- xTry
y <- yTry
z <- zTry
} yield (x, y, z)
Try[A]
与Either[Throwable, A]
同构。换句话说,您可以将Try
视为Either
,其左侧类型为Throwable
,您可以处理左侧类型为Either
的任何Throwable
}作为Try
。同样Option[A]
与Try[A]
同形。因此,您可以将Option
视为忽略错误的Try
。因此,您也可以将其视为Either
。实际上,标准库支持其中一些转换:
//Either to Option
Left[Int, String](1).left.toOption //Some(1)
Right[Int, String]("foo").left.toOption //None
//Try to Option
Try("1".toInt).toOption //Some(1)
Try("foo".toInt).toOption //None
//Option to Either
Some(1).toRight("foo") //Right[String, Int](1)
(None: Option[Int]).toRight("foo") //Left[String, Int]("foo")
标准库不包含从Either
到Try
,从Try
到Either
,或从Option
到Try
的转化。但根据需要丰富Option
,Try
和Either
非常简单:
object OptionTryEitherConversions {
implicit class EitherToTry[L <: Throwable, R](val e: Either[L, R]) extends AnyVal {
def toTry: Try[R] = e.fold(Failure(_), Success(_))
}
implicit class TryToEither[T](val t: Try[T]) extends AnyVal {
def toEither: Either[Throwable, T] = t.map(Right(_)).recover(PartialFunction(Left(_))).get
}
implicit class OptionToTry[T](val o: Option[T]) extends AnyVal {
def toTry(throwable: Throwable): Try[T] = o.map(Right(_)).getOrElse(Left(throwable))
}
}
这将允许你这样做:
import OptionTryEitherConversions._
//Try to Either
Try(1).toEither //Either[Throwable, Int] = Right(1)
Try("foo".toInt).toEither //Either[Throwable, Int] = Left(java.lang.NumberFormatException)
//Either to Try
Right[Throwable, Int](1).toTry //Success(1)
Left[Throwable, Int](new Exception).toTry //Failure(java.lang.Exception)
//Option to Try
Some(1).toTry(new Exception) //Success(1)
(None: Option[Int]).toTry(new Exception) //Failure(java.lang.Exception)
答案 1 :(得分:7)
Either
可被视为Option
的概括。
如果您修复Either
的第一个值,例如将其设置为Unit
,则会获得与Option
基本相同的行为:
Option[X] := Either[Unit, X]
在这种情况下,Left[Unit, X]
对应None
,Right[Unit, X]
对应Some[X]
。
对于Option[X]
,None
会发出某种类型的失败信号,表示X
类型的值,Some[X]
表示成功。
对于Either[Unit, X]
,类型Left[Unit, X]
的实例代表失败,Right[Unit, X]
代表失败。
但是,您可以使用Either
的第一个组件来存储有关为什么失败的更多详细信息,或者一些有助于您从错误中恢复的其他信息。 Option
只给你一个None
,这不是很有用。
但是Either[F,X]
可以返回成功值Right[F, X]
,它基本上只是X
的包装,或者Left[F, X]
中失败的详细描述,其值为{代表失败的{1}}。
这允许您定义更复杂的恢复策略。
例如,看一下Play!-Framework的Form.scala
。
他们在整个地方使用F
,因为他们要么回复用户的表单提交,要么发回部分填写的表单,并注明有用的错误消息。替代方法是使用Either
,如果某些表单字段包含无效输入,则Option[TypeOfFormContent]
将评估为None
。这反过来意味着用户得到类似&#34;错误请求的内容。请再次填写整个表格。&#34;作为回应,这将是非常烦人的。因此,Either
代替Option
,因为它实际上可以跟踪表单提交的确切错误。
Either
的缺点是它不是monad:为了有效地使用它,你总是必须为两个不同的情况传递两个不同的回调。这可能会导致&#34;回调 - 地狱&#34;。因此,应该仔细考虑对失败的确切描述是否有价值。在表单提交失败的情况下,故障的详细描述是有价值的,因为人们不想强迫用户再次重新键入所有内容。在其他情况下,Option
可能更合适,因为人们不想强迫程序员处理不可恢复错误的不必要的详细描述。