What are the differences between Either and Option?

前端 未结 2 886
挽巷
挽巷 2021-02-05 18:22

According to the documentation:

A common use of Either is as an alternative to Option for dealing with possible missing values.

W

2条回答
  •  生来不讨喜
    2021-02-05 18:27

    The nice thing about Either is that you can keep track of the reason something is missing. For example, if you were working with Options, you might be in a situation like this:

    val xOpt = Option(1)
    val yOpt = Option(2)
    val zOpt = None
    
    val tupled = for {
        x <- xOpt
        y <- yOpt
        z <- zOpt
    } yield (x, y, z)
    

    Now, if tupled is None, we don't really know why! If this is an important detail for the rest of the behavior, using Either can help:

    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)
    

    This will return either Left(msg) where the message is the first missing value's corresponding message, or Right(value) for the tupled value. It is conventional to keep use Left for failures and Right for successes.

    Of course, you can also use Either more broadly, not only in situations with missing or exceptional values. There are other situations where Either can help express the semantics of a simple union type.

    The third common idiom to use for exceptional values is the 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] is isomorphic to Either[Throwable, A]. In other words you can treat a Try as an Either with a left type of Throwable, and you can treat any Either that has a left type of Throwable as a Try. Also Option[A] is homomorphic to Try[A]. So you can treat an Option as a Try that ignores errors. Therefore you can also transitively think it as an Either. In fact, the standard library supports some of these transformations:

    //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")   
    

    The standard library does not include the conversions from Either to Try, from Try to Either, or from Option to Try. But it is pretty simple to enrich Option, Try, and Either as needed:

    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))
        }
    }
    

    This would allow you to do:

    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)
    

提交回复
热议问题