Не так давно я почав використовувати Scala замість Java. Частиною процесу "перетворення" між мовами для мене було навчитися використовувати Either
s замість (перевірених) Exception
s. Я кодував цей спосіб деякий час, але останнім часом почав цікавитись, чи справді це кращий шлях.
Одним з основних переваг Either
має більш Exception
краще продуктивність; Exception
необхідно побудувати стек-слід і кидають. Наскільки я розумію, однак, кидання Exception
не найвибагливішої частини, але побудова сліду стека є.
Але тоді, завжди можна побудувати / успадкувати Exception
s scala.util.control.NoStackTrace
, і тим більше, я бачу безліч випадків, коли ліва сторона an 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
допомогою 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 слід використовувати, коли інший випадок методу є "невиключним". По-перше, це дуже і дуже розпливчасте визначення імхо. По-друге, чи дійсно варто синтаксичне покарання? Я маю на увазі, я дійсно не був би проти використання Either
s, якби не синтаксис, який вони представляють.
Either
мені схожа на монаду. Використовуйте його, коли потрібні переваги функціонального складу, які надають монади. А може, ні .
Either
сама по собі не є монадою. Проекція або на лівій стороні або на правій стороні є монадой, але Either
сама по собі не є. Ви можете зробити його монадою, "відхиливши" її вліво чи вправо. Однак тоді ви надаєте певну семантику з обох сторін Either
. Either
Спочатку Скала був неупередженим, але був упередженим досить недавно, так що в даний час це насправді монада, але "монадність" не є властивою властивістю, Either
а скоріше є результатом його упередженості.