What are the differences between Either and Option?

前端 未结 2 855
挽巷
挽巷 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)
    
    0 讨论(0)
  • 2021-02-05 18:45

    Either can be seen as a generalization of Option. If you fix the first value of Either, for example by setting it to Unit, you would get something that behaves in essentially the same way as Option:

    Option[X] := Either[Unit, X]
    

    In this case, Left[Unit, X] would correspond to None, and Right[Unit, X] would correspond to Some[X].

    For Option[X], None would signal some kind of failure to obtain a value of type X, and Some[X] would signal success. For Either[Unit, X], instance of type Left[Unit, X] would represent failure, and Right[Unit, X] would represent success.

    However, you can use the first component of Either to store more detailed information about why something failed, or some additional information that helps you to recover from an error. Option gives you just a None, which is not very useful. But Either[F,X] could return either a success value Right[F, X], which is essentially just a wrapper for X, or a detailed description of failure in Left[F, X], with a value of type F representing the failure.

    This allows you to define more sophisticated recovery strategies. For example, take a look at Form.scala of the Play!-Framework. They use Either all over the place, because they want to either respond to user's form submission, or to send back a partially filled form, annotated with helpful error messages. The alternative to this would be to work with Option[TypeOfFormContent], which would evaluate to None if some of the form fields contained invalid input. This in turn would mean that the user gets something like "Bad Request. Please fill the whole form again." as response, which would be utterly annoying. Therefore, Either is used instead of Option, because it can actually keep track of what exactly went wrong with the form submission.

    The disadvantage of Either is that it is not a monad: in order to work with it effectively, you always have to pass two different callbacks for the two different cases. This can result in a "callback-hell". Therefore, one should think carefully whether an exact description of the failure is that valuable. In the case of a failed form submission, a detailed description of the failure is valuable, because one does not want to force the user to retype everything again. In other cases, Option might be more appropriate, because one does not want to force the programmers to deal with unnecessary detailed descriptions of unrecoverable errors.

    0 讨论(0)
提交回复
热议问题