Either和Option之间有什么区别?

时间:2015-04-16 17:41:34

标签: scala

根据documentation

  

Either的常见用途是作为交易选项的替代方案    可能缺少值。

为什么要使用一个而不是另一个?

2 个答案:

答案 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)

现在,如果tupledNone,我们真的不知道为什么!如果这是其余行为的重要细节,则使用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")   

标准库不包含从EitherTry,从TryEither,或从OptionTry的转化。但根据需要丰富OptionTryEither非常简单:

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]对应NoneRight[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可能更合适,因为人们不想强迫程序员处理不可恢复错误的不必要的详细描述。