Il n'y a pas si longtemps, j'ai commencé à utiliser Scala au lieu de Java. Une partie du processus de «conversion» entre les langues pour moi a été d'apprendre à utiliser Eithers au lieu de (coché) Exceptions. J'ai codé de cette façon pendant un certain temps, mais récemment, j'ai commencé à me demander si c'était vraiment une meilleure façon de procéder.
Un avantage majeur Eithera plus de Exceptionmeilleures performances; un Exceptionbesoin de construire une trace de pile et est lancé. Pour autant que je comprends, cependant, lancer la Exceptionn'est pas la partie exigeante, mais construire la pile-trace l'est.
Mais alors, on peut toujours construire / hériter de Exceptions scala.util.control.NoStackTrace, et plus encore, je vois beaucoup de cas où le côté gauche d'un Eitherest en fait un Exception(renoncer à l'augmentation des performances).
Un autre avantage Eithera la sécurité du compilateur; le compilateur Scala ne se plaindra pas des Exceptions non gérés (contrairement au compilateur Java). Mais si je ne me trompe pas, cette décision est motivée par le même raisonnement qui est discuté dans ce sujet, alors ...
En termes de syntaxe, je pense que Exception-style est beaucoup plus clair. Examinez les blocs de code suivants (les deux réalisant les mêmes fonctionnalités):
Either style:
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 style:
@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 = ???
Ce dernier me semble beaucoup plus propre, et le code gérant l'échec (soit la correspondance de modèle sur Eitherou try-catch) est assez clair dans les deux cas.
Donc ma question est - pourquoi utiliser Eitherplus (vérifié)Exception ?
Mise à jour
Après avoir lu les réponses, je me suis rendu compte que je n'avais peut-être pas présenté le cœur de mon dilemme. Je ne m'inquiète pas de l'absence de la try-catch; on peut soit "attraper" un Exceptionavec Try, soit utiliser catchpour envelopper l'exception avecLeft .
Mon principal problème avec Either/Try survient lorsque j'écris du code qui peut échouer à de nombreux points en cours de route; dans ces scénarios, lorsque je rencontre un échec, je dois propager cet échec dans tout mon code, rendant ainsi le code beaucoup plus lourd (comme illustré dans les exemples susmentionnés).
Il existe en fait une autre façon de casser le code sans Exceptions en utilisant return(qui est en fait un autre "tabou" dans Scala). Le code serait encore plus clair que l' Eitherapproche, et tout en étant un peu moins propre que le Exceptionstyle, il n'y aurait pas à craindre que l' Exceptionart.
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))
}
}
Fondamentalement, les return Leftremplacements throw new Exceptionet la méthode implicite sur l'un ou l'autre rightOrReturnsont un supplément pour la propagation automatique des exceptions dans la pile.
Try. La partie concernant Eithervs Exceptionindique simplement que Eithers devrait être utilisé lorsque l'autre cas de la méthode est "non exceptionnel". Tout d'abord, c'est une définition très, très vague à mon humble avis. Deuxièmement, cela vaut-il vraiment la peine de syntaxe? Je veux dire, cela ne me dérangerait vraiment pas d'utiliser Eithers s'il n'y avait pas la surcharge de syntaxe qu'ils présentent.
Eitherressemble à une monade pour moi. Utilisez-le lorsque vous avez besoin des avantages de composition fonctionnelle qu'offrent les monades. Ou peut - être pas .
Eitheren soi n'est pas une monade. La projection vers le côté gauche ou le côté droit est une monade, mais Eitherne l'est pas en soi. Vous pouvez en faire une monade, en la "biaisant" du côté gauche ou du côté droit, cependant. Cependant, alors vous donnez une certaine sémantique des deux côtés d'un Either. Scala Eitherétait à l'origine impartiale, mais était biaisée assez récemment, de sorte qu'aujourd'hui, c'est en fait une monade, mais la "monadness" n'est pas une propriété inhérente Eithermais plutôt le résultat de son biais.