Vor nicht allzu langer Zeit habe ich angefangen, Scala anstelle von Java zu verwenden. Ein Teil des "Konvertierungs" -Prozesses zwischen den Sprachen bestand für mich darin, zu lernen, Eithers anstelle von (angekreuzten) Exceptions zu verwenden. Ich habe eine Weile auf diese Weise programmiert, aber vor kurzem habe ich mich gefragt, ob das wirklich ein besserer Weg ist.
Ein großer Vorteil Eithergegenüber Exceptionist die bessere Leistung; ein Exceptionmuss einen Stack-Trace erstellen und wird geworfen. Nach meinem Verständnis ist das Werfen von Exceptionzwar nicht der anspruchsvolle Teil, aber das Erstellen des Stack-Trace.
Aber dann kann man Exceptions immer konstruieren / erben scala.util.control.NoStackTrace, und noch mehr sehe ich viele Fälle, in denen die linke Seite eines Eithertatsächlich ein Exception(Verzicht auf den Leistungsschub) ist.
Ein weiterer Vorteil Eitherist die Compilersicherheit. Der Scala-Compiler beschwert sich nicht über nicht behandelte Exceptions (im Gegensatz zum Java-Compiler). Aber wenn ich mich nicht irre, ist diese Entscheidung mit der gleichen Begründung begründet, die in diesem Thema diskutiert wird, also ...
In Bezug auf die Syntax Exceptiondenke ich, dass -style viel klarer ist. Untersuchen Sie die folgenden Codeblöcke (beide erzielen dieselbe Funktionalität):
Either Stil:
def compute(): Either[String, Int] = {
val aEither: Either[String, String] = if (someCondition) Right("good") else Left("bad")
val bEithers: Iterable[Either[String, Int]] = someSeq.map {
item => if (someCondition(item)) Right(item.toInt) else Left("bad")
}
for {
a <- aEither.right
bs <- reduce(bEithers).right
ignore <- validate(bs).right
} yield compute(a, bs)
}
def reduce[A,B](eithers: Iterable[Either[A,B]]): Either[A, Iterable[B]] = ??? // utility code
def validate(bs: Iterable[Int]): Either[String, Unit] = if (bs.sum > 22) Left("bad") else Right()
def compute(a: String, bs: Iterable[Int]): Int = ???
Exception Stil:
@throws(classOf[ComputationException])
def compute(): Int = {
val a = if (someCondition) "good" else throw new ComputationException("bad")
val bs = someSeq.map {
item => if (someCondition(item)) item.toInt else throw new ComputationException("bad")
}
if (bs.sum > 22) throw new ComputationException("bad")
compute(a, bs)
}
def compute(a: String, bs: Iterable[Int]): Int = ???
Letzteres sieht für mich viel sauberer aus, und der Code, der den Fehler behandelt (entweder Pattern-Matching an Eitheroder try-catch), ist in beiden Fällen ziemlich klar.
Also meine Frage ist - warum Eitherüber (überprüft) Exception?
Aktualisieren
Nachdem ich die Antworten gelesen hatte, stellte ich fest, dass ich den Kern meines Dilemmas möglicherweise nicht dargestellt hatte. Mein Anliegen ist nicht das Fehlen der try-catch; kann man entweder „fängt“ ein Exceptionmit Try, oder die Verwendung catchmit der Ausnahme , umwickeln Left.
Mein Hauptproblem mit Either/ Tryentsteht, wenn ich Code schreibe, der an vielen Stellen auf dem Weg fehlschlagen kann. Wenn in diesen Szenarien ein Fehler auftritt, muss ich diesen Fehler auf den gesamten Code übertragen, wodurch der Code umständlicher wird (wie in den oben genannten Beispielen gezeigt).
Es gibt tatsächlich eine andere Möglichkeit, den Code ohne Exceptions zu knacken return(was in der Tat ein weiteres "Tabu" in Scala ist). Der Code wäre immer noch klarer als der EitherAnsatz, und obwohl er etwas weniger klar als der ExceptionStil ist, würde es keine Angst vor nicht gefangenen Exceptions geben.
def compute(): Either[String, Int] = {
val a = if (someCondition) "good" else return Left("bad")
val bs: Iterable[Int] = someSeq.map {
item => if (someCondition(item)) item.toInt else return Left("bad")
}
if (bs.sum > 22) return Left("bad")
val c = computeC(bs).rightOrReturn(return _)
Right(computeAll(a, bs, c))
}
def computeC(bs: Iterable[Int]): Either[String, Int] = ???
def computeAll(a: String, bs: Iterable[Int], c: Int): Int = ???
implicit class ConvertEither[L, R](either: Either[L, R]) {
def rightOrReturn(f: (Left[L, R]) => R): R = either match {
case Right(r) => r
case Left(l) => f(Left(l))
}
}
Grundsätzlich sind die return LeftErsetzungen throw new Exceptionund die implizite Methode für beide rightOrReturneine Ergänzung für die automatische Weitergabe von Ausnahmen im Stapel.
Try. Der Teil über Eithervs gibt Exceptionlediglich an, dass Eithers verwendet werden sollte, wenn der andere Fall der Methode "nicht außergewöhnlich" ist. Erstens ist dies eine sehr, sehr vage Definition imho. Zweitens, ist es die Syntaxstrafe wirklich wert? Ich meine, es würde mir wirklich nichts ausmachen, Eithers zu verwenden, wenn es nicht den Syntax-Overhead gäbe, den sie darstellen.
Eithersieht für mich wie eine Monade aus. Verwenden Sie es, wenn Sie die Vorteile einer funktionellen Zusammensetzung benötigen, die Monaden bieten. Oder vielleicht auch nicht .
Eitheran sich keine Monade. Die Projektion auf die linke oder die rechte Seite ist eine Monade, Eitheran sich jedoch nicht. Sie können machen es eine Monade, durch „Vorspannen“ , um es entweder der linken oder der rechten Seite, though. Dann vermitteln Sie jedoch eine bestimmte Semantik auf beiden Seiten eines Either. Scala's Eitherwar ursprünglich unvoreingenommen, war aber vor kurzem voreingenommen, so dass es heutzutage tatsächlich eine Monade ist, aber die "Monadität" ist keine inhärente Eigenschaft von Either, sondern eine Folge davon, dass sie voreingenommen ist.