Utiliser Either pour traiter les échecs en code Scala

Option monad est une excellente façon de traiter les choses avec ou sans rien dans Scala. Mais que se passe-t-il si l’on doit enregistrer un message lorsque “rien” se produit? Selon la documentation de l’API Scala,

Le type Either est souvent utilisé comme alternative à scala.Option où Left représente un échec (par convention) et Right est apparenté à Some.

Cependant, je n’ai pas eu la chance de trouver les meilleures pratiques en utilisant soit des exemples concrets, soit de bons exemples impliquant soit le traitement des échecs. Enfin, je suis venu avec le code suivant pour mon propre projet:

  def logs: Array[Ssortingng] = { def props: Option[Map[Ssortingng, Any]] = configAdmin.map{ ca => val config = ca.getConfiguration(PID, null) config.properties getOrElse immutable.Map.empty } def checkType(any: Any): Option[Array[Ssortingng]] = any match { case a: Array[Ssortingng] => Some(a) case _ => None } def lookup: Either[(Symbol, Ssortingng), Array[Ssortingng]] = for {val properties  "ConfigurationAdmin service not bound").right val logsParam  "'logs' not defined in the configuration").right val array  "unknown type of 'logs' confguration parameter").right} yield array lookup.fold(failure => { failure match { case ('warning, msg) => log(LogService.WARNING, msg) case ('debug, msg) => log(LogService.DEBUG, msg) case _ => }; new Array[Ssortingng](0) }, success => success) } 

(S’il vous plaît noter que ceci est un extrait d’un projet réel, donc il ne sera pas compilé seul)

Je vous serais reconnaissant de savoir comment vous utilisez Either dans votre code et / ou de meilleures idées sur le refactoring du code ci-dessus.

L’un ou l’autre est utilisé pour renvoyer l’un des deux résultats possibles possibles, contrairement à Option qui est utilisé pour renvoyer un seul résultat significatif ou rien.

Un exemple facile à comprendre est donné ci-dessous (diffusé sur la liste de diffusion Scala il y a quelques temps):

 def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] = try { Right(block) } catch { case ex => Left(ex) } 

Comme son nom l’indique, si l’exécution de “block” est réussie, elle renverra “Right ()”. Sinon, si un object Throwable est lancé, il retournera “Left ()”. Utilisez la correspondance de modèle pour traiter le résultat:

 var s = "hello" throwableToLeft { s.toUpperCase } match { case Right(s) => println(s) case Left(e) => e.printStackTrace } // prints "HELLO" s = null throwableToLeft { s.toUpperCase } match { case Right(s) => println(s) case Left(e) => e.printStackTrace } // prints NullPointerException stack trace 

J’espère que cela pourra aider.

La bibliothèque Scalaz a quelque chose de similaire Soit nommé Validation. Il est plus idiomatique que Soit pour “obtenir soit un résultat valide, soit un échec”.

La validation permet également d’accumuler des erreurs.

Edit: “alike” L’un ou l’autre est complètement faux, car Validation est un foncteur applicatif, et scalaz Soit, nommé \ / (prononcé “disjonction” ou “soit”), est un monad. Le fait que la validation puisse accumuler des erreurs est dû à cette nature. D’un autre côté, / a un caractère “stop early”, s’arrêtant au premier – \ / (lisez-le “left”, ou “error”) qu’il rencontre. Il y a une explication parfaite ici: http://typelevel.org/blog/2014/02/21/error-handling.html

Voir: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

Comme demandé par le commentaire, copiez / collez le lien ci-dessus (quelques lignes supprimées):

 // Extracting success or failure values val s: Validation[Ssortingng, Int] = 1.success val f: Validation[Ssortingng, Int] = "error".fail // It is recommended to use fold rather than pattern matching: val result: Ssortingng = s.fold(e => "got error: " + e, s => "got success: " + s.toSsortingng) s match { case Success(a) => "success" case Failure(e) => "fail" } // Validation is a Monad, and can be used in for comprehensions. val k1 = for { i <- s j <- s } yield i + j k1.toOption assert_≟ Some(2) // The first failing sub-computation fails the entire computation. val k2 = for { i <- f j <- f } yield i + j k2.fail.toOption assert_≟ Some("error") // Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup. // A number of computations are tried. If the all success, a function can combine them into a Success. If any // of them fails, the individual errors are accumulated. // Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor. val k4 = (fNel <**> fNel){ _ + _ } k4.fail.toOption assert_≟ some(nel1("error", "error")) 

L’extrait que vous avez posté semble très artificiel. Vous utilisez l’un ou l’autre dans une situation où:

  1. Il ne suffit pas de savoir que les données ne sont pas disponibles.
  2. Vous devez renvoyer l’un des deux types distincts.

Transformer une exception en une gauche est en effet un cas d’utilisation courant. Sur try / catch, il a l’avantage de conserver le code, ce qui est logique si l’exception est un résultat attendu . La manière la plus courante de manipuler Soit est la correspondance de modèle:

 result match { case Right(res) => ... case Left(res) => ... } 

Une autre façon intéressante de traiter Either est quand il apparaît dans une collection. Lorsque vous faites une carte sur une collection, lancer une exception peut ne pas être viable et vous pouvez vouloir renvoyer des informations autres que “non possible”. Utiliser un Either vous permet de le faire sans surcharger l’algorithme:

 val list = ( library \\ "books" map (book => if (book \ "author" isEmpty) Left(book) else Right((book \ "author" toList) map (_ text)) ) ) 

Ici, nous obtenons une liste de tous les auteurs de la bibliothèque, ainsi qu’une liste de livres sans auteur. Nous pouvons donc ensuite le traiter en conséquence:

 val authorCount = ( (Map[Ssortingng,Int]() /: (list filter (_ isRight) map (_.right.get))) ((map, author) => map + (author -> (map.getOrElse(author, 0) + 1))) toList ) val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation 

Donc, basique Soit l’utilisation va comme ça. Ce n’est pas une classe particulièrement utile, mais si c’était le cas, vous l’auriez déjà vu auparavant. D’autre part, ce n’est pas inutile non plus.