Certaines langues prétendent n'avoir «aucune exception d'exécution» comme un net avantage sur les autres langues qui en disposent.
Je suis confus à ce sujet.
L'exception d'exécution n'est qu'un outil, pour autant que je sache, et lorsqu'il est bien utilisé:
- vous pouvez communiquer des états "sales" (lancer des données inattendues)
- en ajoutant une pile, vous pouvez pointer vers la chaîne d'erreur
- vous pouvez faire la distinction entre l'encombrement (par exemple, renvoyer une valeur vide sur une entrée non valide) et une utilisation non sécurisée qui nécessite l'attention d'un développeur (par exemple, lever une exception sur une entrée non valide)
- vous pouvez ajouter des détails à votre erreur avec le message d'exception fournissant d'autres détails utiles aidant aux efforts de débogage (théoriquement)
Par contre je trouve vraiment difficile de déboguer un logiciel qui "avale" les exceptions. Par exemple
try {
myFailingCode();
} catch {
// no logs, no crashes, just a dirty state
}
La question est donc: quel est l'avantage théorique fort d'avoir "aucune exception d'exécution"?
Exemple
Aucune erreur d'exécution dans la pratique. Pas de null. Aucun indéfini n'est pas une fonction.
Réponses:
Les exceptions ont une sémantique extrêmement limitative. Ils doivent être manipulés exactement là où ils sont lancés, ou dans la pile d'appels directs vers le haut, et il n'y a aucune indication au programmeur au moment de la compilation si vous oubliez de le faire.
Comparez cela à Elm où les erreurs sont codées en tant que résultats ou Maybes , qui sont les deux valeurs . Cela signifie que vous obtenez une erreur de compilation si vous ne gérez pas l'erreur. Vous pouvez les stocker dans une variable ou même une collection pour différer leur manipulation à un moment opportun. Vous pouvez créer une fonction pour gérer les erreurs d'une manière spécifique à l'application au lieu de répéter des blocs try-catch très similaires partout. Vous pouvez les enchaîner dans un calcul qui ne réussit que si toutes ses parties réussissent, et elles ne doivent pas être entassées dans un bloc d'essai. Vous n'êtes pas limité par la syntaxe intégrée.
Cela ne ressemble en rien à «avaler des exceptions». Cela rend les conditions d'erreur explicites dans le système de types et fournit une sémantique alternative beaucoup plus flexible pour les gérer.
Prenons l'exemple suivant. Vous pouvez coller ceci dans http://elm-lang.org/try si vous souhaitez le voir en action.
Notez que
String.toInt
dans lacalculate
fonction a la possibilité d'échouer. En Java, cela a le potentiel de lever une exception d'exécution. Lorsqu'il lit les entrées utilisateur, il a assez de chances de le faire. Au lieu de cela, Elm m'oblige à y faire face en retournant unResult
, mais remarquez que je n'ai pas à m'en occuper tout de suite. Je peux doubler l'entrée et la convertir en chaîne, puis rechercher une mauvaise entrée dans lagetDefault
fonction. Cet endroit est beaucoup mieux adapté au contrôle que le point où l'erreur s'est produite ou vers le haut dans la pile des appels.La façon dont le compilateur force notre main est également beaucoup plus fine que les exceptions vérifiées de Java. Vous devez utiliser une fonction très spécifique comme
Result.withDefault
extraire la valeur souhaitée. Bien que, techniquement, vous puissiez abuser de ce type de mécanisme, cela ne sert à rien. Puisque vous pouvez reporter la décision jusqu'à ce que vous connaissiez un bon message par défaut / d'erreur à mettre, il n'y a aucune raison de ne pas l'utiliser.la source
That means you get a compiler error if you don't handle the error.
- Eh bien, c'était le raisonnement derrière Checked Exceptions en Java, mais nous savons tous à quel point cela a fonctionné.Maybe
,Either
, etc. regards Elm comme il prend une page de langues telles que ML, OCaml ou Haskell.x = some_func()
, je ne dois rien faire à moins que je veux examiner la valeurx
, dans ce cas , je peux vérifier si j'ai une erreur ou une valeur « valide »; de plus, c'est une erreur de type statique pour tenter d'utiliser l'un à la place de l'autre, donc je ne peux pas le faire. Si les types d'Elm fonctionnent quelque chose comme d'autres langages fonctionnels, je peux réellement faire des choses comme composer des valeurs à partir de différentes fonctions avant même de savoir si ce sont des erreurs ou non! C'est typique des langages FP.Pour comprendre cette affirmation, nous devons d'abord comprendre ce qu'un système de type statique nous achète. En substance, ce qu'un système de type statique nous donne est une garantie: si les vérifications de type de programme, une certaine classe de comportements d'exécution ne peut pas se produire.
Cela semble inquiétant. Eh bien, un vérificateur de type est similaire à un vérificateur de théorème. (En fait, selon l'isomorphisme de Curry-Howard, c'est la même chose.) Une chose qui est très particulière à propos des théorèmes est que lorsque vous prouvez un théorème, vous prouvez exactement ce que dit le théorème, pas plus. (C'est par exemple pourquoi, lorsque quelqu'un dit "J'ai prouvé que ce programme est correct", vous devez toujours demander "veuillez définir" correct "".) La même chose est vraie pour les systèmes de type. Lorsque nous disons "un programme est sûr pour le type", nous ne voulons pas dire qu'aucune erreur possible ne peut se produire. Nous pouvons seulement dire que les erreurs que le système de type nous promet d'éviter ne peuvent pas se produire.
Ainsi, les programmes peuvent avoir une infinité de comportements d'exécution différents. Parmi ceux-ci, un nombre infini de ceux-ci sont utiles, mais aussi un nombre infini de ceux-ci sont "incorrects" (pour diverses définitions de "l'exactitude"). Un système de type statique nous permet de prouver qu'un certain ensemble fini et fixe de ces infiniment de comportements d'exécution incorrects ne peut pas se produire.
La différence entre les différents types de systèmes réside essentiellement dans le fait de savoir combien, et combien de comportements d'exécution complexes ils peuvent ne pas se produire. Les systèmes de type faible tels que Java ne peuvent prouver que des choses très basiques. Par exemple, Java peut prouver qu'une méthode qui est typée comme retournant un
String
ne peut pas retourner unList
. Mais, par exemple, il ne peut pas prouver que la méthode ne reviendra pas. Il ne peut pas non plus prouver que la méthode ne lèvera pas d'exception. Et il ne peut pas prouver qu'il ne renverra pas le mauvaisString
- toutString
satisfera le vérificateur de type. (Et, bien sûr, mêmenull
satisfera aussi bien.) Il y a même des choses que Java ne peut pas prouver très simple, ce qui est la raison pour laquelle nous avons des exceptions commeArrayStoreException
,ClassCastException
ou tout le monde le favori, leNullPointerException
.Des systèmes de types plus puissants comme Agda peuvent également prouver des choses comme «renverra la somme des deux arguments» ou «renvoie la version triée de la liste passée en argument».
Maintenant, ce que les concepteurs d'Elm entendent par la déclaration selon laquelle ils n'ont pas d'exceptions d'exécution, c'est que le système de type d'Elm peut prouver l'absence de (une partie importante) des comportements d'exécution qui, dans d'autres langues, ne peuvent pas être prouvés comme ne se produisant pas et pourraient donc conduire à un comportement erroné lors de l'exécution (ce qui dans le meilleur des cas signifie une exception, dans le pire des cas, un crash, et dans le pire des cas, aucun crash, aucune exception, et juste un résultat silencieusement erroné).
Donc, ils ne disent pas "nous n'implémentons pas d'exceptions". Ils disent que "les choses qui seraient des exceptions d'exécution dans des langages typiques que les programmeurs typiques venant avec Elm auraient expérimentés, sont prises par le système de type". Bien sûr, quelqu'un venant d'Idris, Agda, Guru, Epigram, Isabelle / HOL, Coq ou d'autres langues similaires verra Elm comme assez faible en comparaison. L'instruction s'adresse davantage aux programmeurs Java, C♯, C ++, Objective-C, PHP, ECMAScript, Python, Ruby, Perl,… typiques.
la source
Elm ne peut garantir aucune exception d'exécution pour la même raison C ne peut garantir aucune exception d'exécution: le langage ne prend pas en charge le concept d'exceptions.
Elm a un moyen de signaler les conditions d'erreur lors de l'exécution, mais ce système ne fait pas exception, il s'agit de "Résultats". Une fonction qui peut échouer renvoie un "résultat" qui contient soit une valeur régulière soit une erreur. Elms est fortement typé, c'est donc explicite dans le système de type. Si une fonction retourne toujours un entier, elle a le type
Int
. Mais s'il retourne un entier ou échoue, le type de retour estResult Error Int
. (La chaîne est le message d'erreur.) Cela vous oblige à gérer explicitement les deux cas sur le site d'appel.Voici un exemple de l'introduction (un peu simplifié):
La fonction
toInt
peut échouer si l'entrée n'est pas analysable, donc son type de retour l'estResult String int
. Pour obtenir la valeur entière réelle, vous devez "décompresser" via la correspondance de modèles, ce qui vous oblige à gérer les deux cas.Les résultats et les exceptions font fondamentalement la même chose, la différence importante étant les «défauts». Les exceptions bouillonneront et mettront fin au programme par défaut, et vous devez les intercepter explicitement si vous voulez les gérer. Le résultat est dans l'autre sens - vous êtes obligé de les gérer par défaut, vous devez donc les passer explicitement jusqu'au sommet si vous voulez qu'ils terminent le programme. Il est facile de voir comment ce comportement peut conduire à un code plus robuste.
la source
doSomeStuff(x: Int): Int
. Normalement, vous vous attendez à ce qu'il renvoie unInt
, mais peut-il également lever une exception? Sans regarder son code source, vous ne pouvez pas savoir. En revanche, un langage B qui code des erreurs via des types peut avoir la même fonction déclarée comme ceci:doSomeStuff(x: Int): ErrorOrResultOfType<Int>
(dans Elm ce type est en fait nomméResult
). Contrairement au premier cas, il est maintenant immédiatement évident que la fonction peut échouer et vous devez la gérer explicitement.this is how you program in languages such as ML or Haskell
À Haskell, oui; ML, non. Robert Harper, un contributeur majeur au chercheur Standard ML et le langage de programmation, considère les exceptions soient utiles . Les types d'erreur peuvent entraver la composition de la fonction dans les cas où vous pouvez garantir qu'une erreur ne se produira pas. Les exceptions ont également des performances différentes. Vous ne payez pas pour les exceptions qui ne sont pas levées, mais vous payez pour vérifier les valeurs d'erreur à chaque fois, et les exceptions sont un moyen naturel d'exprimer le retour en arrière dans certains algorithmesTout d'abord, veuillez noter que votre exemple d'exceptions de «déglutition» est en général une pratique terrible et complètement sans rapport avec l'absence d'exceptions de temps d'exécution; lorsque vous y réfléchissez, vous avez eu une erreur d'exécution, mais vous avez choisi de la masquer et de ne rien y faire. Cela entraînera souvent des bogues difficiles à comprendre.
Cette question pourrait être interprétée de plusieurs façons, mais comme vous avez mentionné Elm dans les commentaires, le contexte est plus clair.
Elm est, entre autres, un langage de programmation typé statiquement . L'un des avantages de ce type de systèmes de types est que de nombreuses classes d'erreurs (mais pas toutes) sont détectées par le compilateur, avant que le programme ne soit réellement utilisé. Certains types d'erreurs peuvent être encodés en types (tels que Elm
Result
etTask
), au lieu d'être levés comme exceptions. C'est ce que les concepteurs d'Elm veulent dire: de nombreuses erreurs seront détectées au moment de la compilation plutôt qu'au "moment de l'exécution", et le compilateur vous forcera à les traiter au lieu de les ignorer et d'espérer le meilleur. Il est clair pourquoi c'est un avantage: mieux vaut que le programmeur prenne conscience d'un problème avant que l'utilisateur ne le fasse.Notez que lorsque vous n'utilisez pas d'exceptions, les erreurs sont codées d'autres manières moins surprenantes. D'après la documentation d'Elm :
Les concepteurs d'orme sont un peu audacieux en déclarant "aucune exception de temps d'exécution" , bien qu'ils le qualifient de "en pratique". Ce qu'ils signifient probablement "moins d'erreurs inattendues que si vous codiez en javascript".
la source
Result
etTask
qui ressemblent beaucoup aux plus familiersEither
et àFuture
d'autres langues. Contrairement aux exceptions, les valeurs de ces types peuvent être combinées et à un moment donné, vous devez les gérer explicitement: représentent-elles une valeur valide ou une erreur? Je ne lis pas les esprits, mais ce manque de surprendre le programmeur est probablement ce que les concepteurs d'Elm entendaient par "aucune exception de temps d'exécution" :)L'orme affirme:
Mais vous posez des questions sur les exceptions d' exécution . Il y a une différence.
Dans Elm, rien ne renvoie un résultat inattendu. Vous ne pouvez PAS écrire un programme valide dans Elm qui génère des erreurs d'exécution. Ainsi, vous n'avez pas besoin d'exceptions.
Donc, la question devrait être:
Si vous pouvez écrire du code qui n'a jamais d'erreurs d'exécution, vos programmes ne se bloqueront jamais.
la source