Either和Option之间有什么区别?

Pet*_*ton 10 scala

根据文件:

Either的一个常见用途是作为Option的替代方法来处理可能的缺失值.

你为什么要用另一个呢?

Ben*_*ich 16

好处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)
Run Code Online (Sandbox Code Playgroud)

现在,如果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)
Run Code Online (Sandbox Code Playgroud)

这将返回Left(msg)消息是第一个缺失值的相应消息的位置,或者返回Right(value)为tupled值.通常保持Left用于失败和Right成功.

当然,您也可以Either更广泛地使用,不仅在缺少值或特殊值的情况下.还有其他情况Either可以帮助表达简单联合类型的语义.

使用特殊值的第三个常用习语是Trymonad:

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)
Run Code Online (Sandbox Code Playgroud)

Try[A]是同构的Either[Throwable, A].换句话说,您可以将a Try视为Either左类型Throwable,并且可以将任何Either左类型Throwable视为a Try.也是Option[A]同形的Try[A].因此,您可以将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")   
Run Code Online (Sandbox Code Playgroud)

标准库不包括从转换EitherTry,从TryEither,或者OptionTry.但是,根据需要,丰富Option,非常简单:TryEither

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))
    }
}
Run Code Online (Sandbox Code Playgroud)

这将允许您:

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)
Run Code Online (Sandbox Code Playgroud)


And*_*kin 7

Either可以看作是一种概括Option.如果您修复了第一个值Either,例如通过将其设置为Unit,则可以获得与以下内容基本相同的方式Option:

Option[X] := Either[Unit, X]
Run Code Online (Sandbox Code Playgroud)

在这种情况下,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]一个F表示失败的类型值的故障的详细描述.

这允许您定义更复杂的恢复策略.例如,看看Form.scalaPlay!-Framework.他们使用Either到处都是,因为他们要么响应用户的表单提交,要么发回一个部分填写的表单,注释有用的错误消息.替代方法是使用Option[TypeOfFormContent],None如果某些表单字段包含无效输入,则会评估.这反过来意味着用户会得到类似"错误请求.请重新填写整个表单"的内容.作为回应,这将是非常烦人的.因此,Either使用而不是Option,因为它实际上可以跟踪表单提交到底出了什么问题.

缺点Either是它不是monad:为了有效地使用它,你总是必须为两个不同的情况传递两个不同的回调.这可能导致"回调 - 地狱".因此,应该仔细考虑对失败的确切描述是否有价值.在表单提交失败的情况下,故障的详细描述是有价值的,因为人们不想强迫用户再次重新键入所有内容.在其他情况下,Option可能更合适,因为人们不想强迫程序员处理不可恢复错误的不必要的详细描述.