2015-04-16 13 views

Antwort

10

Die nette Sache über Either ist, dass Sie den Grund verfolgen können, etwas fehlt. wenn Sie mit Options Zum Beispiel arbeiten, könnten Sie in einer Situation wie diese sein:

val xOpt = Option(1) 
val yOpt = Option(2) 
val zOpt = None 

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

Nun, wenn tupledNone ist, wissen wir nicht wirklich wissen, warum! Wenn dies für den Rest des Verhaltens ein wichtiges Detail ist, mit Either helfen:

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) 

Dies kehrt entweder Left(msg), wo die Nachricht den ersten fehlenden Wert der entsprechenden Nachricht ist, oder Right(value) für den fach vervielfachten Wert. Es ist üblich, Left für Fehler und Right für Erfolge zu verwenden.

Natürlich können Sie auch Either breiter verwenden, nicht nur in Situationen mit fehlenden oder außergewöhnlichen Werten. Es gibt andere Situationen, in denen Either helfen kann, die Semantik eines einfachen Union-Typs auszudrücken.

Das dritte gemeinsame Idiom für außergewöhnliche Werte zu verwenden, ist der Try monadisch:

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] isomorph zu Either[Throwable, A]. Mit anderen Worten, Sie können eine Try als Either mit einem linken Typ von Throwable behandeln, und Sie können jede Either behandeln, die einen linken Typ von Throwable als Try hat. Auch Option[A] ist homomorph zu Try[A]. So können Sie eine Option als Try behandeln, die Fehler ignoriert. Daher kann man es auch transitiv als Either bezeichnen. Tatsächlich unterstützt die Standard-Bibliothek einige dieser Transformationen:

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

Die Standardbibliothek enthält nicht die Konvertierungen von Either zu Try, von Try zu Either oder Option-Try. Aber es ist ziemlich einfach Option zu bereichern, Try und Either nach Bedarf:

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

Dies würde erlauben Sie zu tun:

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

Either kann als eine Verallgemeinerung von Option angesehen werden. Wenn Sie den ersten Wert von Either, beispielsweise beheben, indem es Unit Einstellung, würden Sie etwas, das im Wesentlichen die gleiche Art und Weise verhält sich wie Option:

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

In diesem Fall Left[Unit, X]-None entsprechen würde, und Right[Unit, X] würde Some[X] entsprechen.

Für Option[X], None würde eine Art Fehlersignal einen Wert vom Typ X, zu erhalten und Some[X] würde Erfolg signalisieren. Für Either[Unit, X] würde eine Instanz des Typs Left[Unit, X] einen Fehler darstellen und Right[Unit, X] würde den Erfolg darstellen.

Sie können jedoch die erste Komponente von Either verwenden detaillierte Informationen über zu speichern warum etwas fehlgeschlagen ist, oder einige zusätzliche Informationen, die Sie von einem Fehler zu erholen hilft. Option gibt Ihnen nur eine None, die nicht sehr nützlich ist. Aber Either[F,X] konnte entweder einen Erfolgswert Right[F, X], der im Wesentlichen nur ein Wrapper für X ist, oder eine detaillierte Beschreibung des Fehlers in Left[F, X] mit einem Wert des Typs F, der den Fehler darstellt, zurückgeben.

Dadurch können Sie komplexere Wiederherstellungsstrategien definieren. Schauen Sie sich beispielsweise Form.scala des Play! -Frameworks an. Sie verwenden Either überall, weil sie entweder auf die Formularübermittlung des Benutzers reagieren oder ein teilweise ausgefülltes Formular zurücksenden möchten, das mit hilfreichen Fehlermeldungen versehen ist. Die Alternative dazu wäre, mit Option[TypeOfFormContent] zu arbeiten, was zu None auswerten würde, wenn einige der Formularfelder ungültige Eingabe enthielten. Dies würde wiederum bedeuten, dass der Benutzer etwas wie "Falsche Anfrage" erhält. Bitte füllen Sie das gesamte Formular erneut aus. " als Antwort, die absolut nervig wäre. Daher wird Either anstelle von Option verwendet, weil es tatsächlich verfolgen kann, was bei der Formularübermittlung genau falsch gelaufen ist.

Der Nachteil von Either ist, dass es keine Monade ist: Um effektiv damit zu arbeiten, müssen Sie immer zwei verschiedene Rückrufe für die zwei verschiedenen Fälle passieren. Dies kann zu einer "Rückruf-Hölle" führen. Daher sollte man sorgfältig überlegen, ob eine genaue Beschreibung des Fehlers so wertvoll ist. Im Falle einer fehlgeschlagenen Formularübergabe ist eine detaillierte Beschreibung des Fehlers hilfreich, da der Benutzer nicht gezwungen werden soll, alles erneut einzugeben. In anderen Fällen kann Option geeigneter sein, da die Programmierer nicht gezwungen werden, unnötige detaillierte Beschreibungen von nicht behebbaren Fehlern zu behandeln.

+1

Andrey Tyukin ist zurückgekehrt! Ich muss allerdings zugeben - ich bin ein wenig enttäuscht, dass Sie für Ihre Antwort kein [weiteres fantastisches Diagramm] (http://stackoverflow.com/a/19739576/1427124) zusammengestellt haben. – DaoWen

+1

@DaoWen: Ich denke, ich sollte definitiv etwas gegen mein "Internet-Alter-Ego" tun, denn momentan scheint es so, als wäre ein lächerliches Gif-Bild eines brennenden veganen Pandas die zweitwichtigste Idee, die ich je hatte. Ich werde in drei Monaten etwas unternehmen. –

Verwandte Themen