Не так давно я начал использовать Scala вместо Java. Частью процесса «преобразования» между языками для меня было обучение использованию Either
s вместо (проверенных) Exception
s. Некоторое время я так кодировал, но недавно начал задаваться вопросом, действительно ли это лучший путь.
Одним из основных преимуществ Either
имеет более Exception
лучше производительность; Exception
необходимо построить стек-след и бросают. Насколько я понимаю, бросать Exception
не является сложной задачей, но построение трассировки стека.
Но потом, всегда можно построить / унаследует Exception
s с scala.util.control.NoStackTrace
, и даже более того, я вижу много случаев , когда левая сторона Either
фактически являетсяException
(исключая повышение производительности).
Еще одно преимущество Either
- безопасность компилятора; компилятор Scala не будет жаловаться на необработанные Exception
s (в отличие от компилятора Java). Но если я не ошибаюсь, это решение обосновано теми же соображениями, которые обсуждаются в этой теме, так что ...
С точки зрения синтаксиса, я чувствую, что Exception
-стиль намного понятнее. Изучите следующие блоки кода (оба с одинаковыми функциями):
Either
стиль:
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
стиль:
@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 = ???
Последнее выглядит намного чище для меня, и код обрабатывает ошибку (либо сопоставление с шаблоном, Either
либоtry-catch
), достаточно ясен в обоих случаях.
Так что мой вопрос - зачем использовать Either
более (проверено)Exception
?
Обновить
Прочитав ответы, я понял, что, возможно, мне не удалось представить суть моей дилеммы. Мое беспокойство не связано с отсутствием try-catch
; Можно либо «поймать» оператор Exception
with Try
, либо использовать его catch
для обёртывания исключения Left
.
Моя главная проблема с Either
/ Try
возникает, когда я пишу код, который может потерпеть неудачу во многих местах; в этих сценариях, при обнаружении сбоя, я должен распространять этот сбой по всему моему коду, что делает код более громоздким (как показано в вышеупомянутых примерах).
На самом деле есть еще один способ взломать код без Exception
s с помощью return
(что на самом деле является еще одним «табу» в Scala). Код был бы все же более понятным, чем Either
подход, и, будучи немного менее чистым, чем Exception
стиль, не было бы страха перед не пойманными Exception
s.
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))
}
}
По сути, return Left
замены throw new Exception
и неявный метод в обоих случаях rightOrReturn
являются дополнением к автоматическому распространению исключений в стеке.
Try
. Часть о Either
vs Exception
просто утверждает, что Either
s следует использовать, когда другой случай метода является «неисключительным». Во-первых, это очень, очень расплывчатое определение imho. Во-вторых, действительно ли стоит штраф за синтаксис? Я имею в виду, я бы не стал возражать против использования Either
s, если бы не накладные расходы на синтаксис, которые они представляют.
Either
выглядит как монада для меня. Используйте его, когда вам нужны преимущества функциональной композиции, которые дают монады. Или, может быть, нет .
Either
сам по себе не является монадой. Проекция либо на левой стороне или на правой стороне является монадой, но Either
сама по себе не является. Вы можете сделать это монадой, «смещая» ее либо к левой стороне, либо к правой стороне. Однако затем вы привносите определенную семантику по обе стороны от Either
. Either
Изначально Scala был беспристрастным, но был предвзятым сравнительно недавно, так что в настоящее время он фактически является монадой, но «монадность» не является присущим свойством, Either
а скорее является результатом его предвзятости.