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