Je suis le cours Scala sur Coursera. J'ai également commencé à lire le livre Scala d'Odersky).
Ce que j'entends souvent, c'est que ce n'est pas une bonne idée de lever des exceptions dans les langages fonctionnels, car cela rompt le flux de contrôle et nous retournons généralement un Either avec l'échec ou le succès. Il semble également que Scala 2.10 fournira le Try qui va dans ce sens.
Mais dans le livre et le cours, Martin Odersky ne semble pas dire (du moins pour l'instant) que les exceptions sont mauvaises, et il les utilise beaucoup. J'ai aussi remarqué les méthodes affirmer/exiger ...
Enfin je suis un peu confus car j'aimerais suivre les bonnes pratiques mais elles ne sont pas claires et le langage semble aller dans les deux sens ...
Quelqu'un peut-il m'expliquer ce que je dois utiliser dans ce cas?
La règle de base est d'utiliser des exceptions pour quelque chose de vraiment exceptionnel **. Pour un échec "ordinaire", il est préférable d'utiliser Option
ou Either
. Si vous vous connectez avec Java où des exceptions sont levées lorsque quelqu'un éternue dans le mauvais sens, vous pouvez utiliser Try
pour vous protéger.
Prenons quelques exemples.
Supposons que vous ayez une méthode qui récupère quelque chose d'une carte. Qu'est-ce qui pourrait mal se passer? Eh bien, quelque chose de dramatique et dangereux comme un segfault* débordement de pile, ou quelque chose attendu comme l'élément n'est pas trouvé. Tu laisserais le segfault le débordement de pile lève une exception, mais si vous ne trouvez simplement pas d'élément, pourquoi ne pas retourner un Option[V]
au lieu de la valeur ou d'une exception (ou null
)?
Supposons maintenant que vous écrivez un programme où l'utilisateur est censé entrer un nom de fichier. Maintenant, si vous n'allez pas simplement renflouer instantanément le programme en cas de problème, un Either
est le chemin à parcourir:
def main(args: Array[String]) {
val f = {
if (args.length < 1) Left("No filename given")
else {
val file = new File(args(0))
if (!file.exists) Left("File does not exist: "+args(0))
else Right(file)
}
}
// ...
}
Supposons maintenant que vous souhaitiez analyser une chaîne avec des nombres séparés par des espaces.
val numbers = "1 2 3 fish 5 6" // Uh-oh
// numbers.split(" ").map(_.toInt) <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt)) // Caught it!
val good = tried.collect{ case Success(n) => n }
Vous avez donc trois façons (au moins) de gérer différents types de pannes: Option
car cela a fonctionné/n'a pas fonctionné, dans les cas où le non-fonctionnement est un comportement attendu, pas une panne choquante et alarmante; Either
pour quand les choses peuvent fonctionner ou non (ou, vraiment, dans tous les cas où vous avez deux options mutuellement exclusives) et que vous souhaitez enregistrer des informations sur ce qui s'est mal passé; et Try
lorsque vous ne voulez pas que le mal de tête de l'exception se gère vous-même, mais que vous avez toujours besoin d'interfacer avec du code qui est à la hauteur des exceptions.
Soit dit en passant, les exceptions constituent de bons exemples - vous les trouverez donc plus souvent dans un manuel ou du matériel d'apprentissage qu'ailleurs, je pense: les exemples de manuels sont très souvent incomplets, ce qui signifie que les problèmes graves qui seraient normalement évités par une conception soignée devraient au lieu d'être signalé en lançant une exception.
*Edit: Segfaults plante la JVM et ne devrait jamais arriver quel que soit le bytecode; même une exception ne vous aidera pas. Je voulais dire débordement de pile.
**Edit: Les exceptions (sans trace de pile) sont également utilisées pour le flux de contrôle dans Scala - elles sont en fait un mécanisme assez efficace, et elles permettent des choses comme les instructions break
définies par la bibliothèque et un return
qui revient de votre méthode même si le contrôle est effectivement passé dans une ou plusieurs fermetures. Généralement, vous ne devriez pas vous en préoccuper vous-même, sauf pour vous rendre compte que la capture de allThrowable
s n'est pas une super idée car vous pourriez intercepter une de ces exceptions de flux de contrôle par erreur.
C'est donc l'un de ces endroits où Scala échange spécifiquement la pureté fonctionnelle pour une facilité de transition - de/interopérabilité - avec les langages et environnements hérités, en particulier Java. La pureté fonctionnelle est rompue par des exceptions, car ils brisent l'intégrité référentielle et rendent impossible le raisonnement équationnel. (Bien sûr, les récursions sans terminaison font de même, mais peu de langues sont disposées à appliquer les restrictions qui les rendraient impossibles.) Pour conserver la pureté fonctionnelle, vous utilisez l'option/Peut-être/Soit/Essayer/Validation, qui codent tous le succès ou l'échec comme un type référentiellement transparent, et utilisent les diverses fonctions d'ordre supérieur qu'ils fournissent ou la syntaxe spéciale de monade des langages sous-jacents pour rendre les choses plus claires. Ou, dans Scala, vous peut simplement décider d'abandonner la pureté fonctionnelle, sachant que cela pourrait rendre les choses plus faciles à court terme mais plus difficiles à long terme. Cela est similaire à l'utilisation de "null" dans Scala, ou de collections mutables, ou de "var" locaux. Légèrement honteux , et ne faites pas grand chose, mais tout le monde est dans les délais.