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