web-dev-qa-db-fra.com

Quel est l'avantage d'avoir «aucune exception d'exécution», comme les revendications d'ELM?

Certaines langues prétendent avoir "aucune exception d'exécution" comme un avantage clair sur d'autres langues qui les ont.

Je suis confus sur la question.

L'exception d'exécution n'est qu'un outil, autant que je sache, et lorsqu'il est utilisé bien:

  • vous pouvez communiquer des états "sales" (jetant des données inattendues)
  • ajout de la pile que vous pouvez indiquer la chaîne d'erreur
  • vous pouvez distinguer entre l'encombrement (par exemple renvoyer une valeur vide sur une entrée non valide) et une utilisation dangereuse qui nécessite une attention particulière d'un développeur (par exemple une exception de lancement sur une entrée non valide)
  • vous pouvez ajouter des détails à votre erreur avec le message d'exception fournissant de nouvelles informations utiles pour aider les efforts de débogage (théoriquement)

D'autre part, je trouve vraiment difficile de déboguer un logiciel qui "avale" des exceptions. Par exemple.

try { 
  myFailingCode(); 
} catch {
  // no logs, no crashes, just a dirty state
}

La question est donc la suivante: quel est l'avantage fort et théorique d'avoir "aucune exception d'exécution"?


Exemple

https://guide.elm-lang.org/

Aucune erreur d'exécution dans la pratique. Pas de null. Aucun indéfini n'est pas une fonction.

17
atoth

Les exceptions ont une sémantique extrêmement limitée. Ils doivent être traités exactement où ils sont jetés ou dans la pile d'appel direct vers le haut, et il n'y a aucune indication au programmeur à la compilation si vous oubliez de le faire.

Contrairement ceci avec elm où des erreurs sont codées comme résultats ou MAYBES , qui sont les deux valeurs . Cela signifie que vous obtenez une erreur de compilateur 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 de manière spécifique à l'application au lieu de répéter des blocs d'essais très similaires sur la place. Vous pouvez les chaîner dans un calcul qui ne réussit que si toutes ses parties réussissent, et ils ne doivent pas nécessairement être entassés dans un bloc d'essai. Vous n'êtes pas limité par la syntaxe intégrée.

Ce n'est rien comme "avaler des exceptions". Il effectue des conditions d'erreur explicites dans le système de type et fournissant une sémantique alternative beaucoup plus flexible pour les gérer.

Considérez l'exemple suivant. Vous pouvez la coller dans http://elm-lang.org/try Si vous souhaitez le voir en action.

import Html exposing (Html, Attribute, beginnerProgram, text, div, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String

main =
  beginnerProgram { model = "", view = view, update = update }

-- UPDATE

type Msg = NewContent String

update (NewContent content) oldContent =
  content

getDefault = Result.withDefault "Please enter an integer" 

double = Result.map (\x -> x*2)

calculate = String.toInt >> double >> Result.map toString >> getDefault

-- VIEW

view content =
  div []
    [ input [ placeholder "Number to double", onInput NewContent, myStyle ] []
    , div [ myStyle ] [ text (calculate content) ]
    ]

myStyle =
  style
    [ ("width", "100%")
    , ("height", "40px")
    , ("padding", "10px 0")
    , ("font-size", "2em")
    , ("text-align", "center")
    ]

Noter la String.toInt Dans la fonction calculate a la possibilité d'échouer. En Java, cela a le potentiel de lancer une exception d'exécution. Comme il lit les entrées de l'utilisateur, il en a une assez bonne chance. Elm me force à faire face à cela en retournant un Result, mais remarquez que je n'ai pas à gérer tout de suite. Je peux doubler l'entrée et le convertir en chaîne puis Vérifiez la mauvaise entrée dans la fonction getDefault. Cet endroit est bien mieux adapté au chèque que le point sur lequel l'erreur s'est produite ou vers le haut dans la pile d'appels.

La manière dont le compilateur oblige notre main est également beaucoup plus fin que les exceptions vérifiées de Java. Vous devez utiliser une fonction très spécifique comme Result.withDefault Pour extraire la valeur que vous voulez. Bien que techniquement, vous pourriez abuser de ce type de mécanisme, il n'y a pas beaucoup de point. Puisque vous pouvez différer la décision jusqu'à ce que vous sachiez un bon message par défaut/erreur d'erreur, il n'y a aucune raison de ne pas l'utiliser.

28
Karl Bielefeldt

Afin de comprendre cette déclaration, nous devons d'abord comprendre ce qu'est un système de type statique nous achète. En substance, ce qu'un système de type statique nous donne, est une garantie: iff 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 théorique. (En fait, par le curry-howard-isomorphisme, ils sont la même chose.) Une chose qui est très particulière sur les théorèmes est que lorsque vous prouvez un théorème, vous prouvez exactement ce que le théorème dit, pas plus. (C'est par exemple, pourquoi, quand quelqu'un dit "j'ai prouvé ce programme correct", vous devez toujours demander "s'il vous plaît définir" correct "".) Il en va de même pour les systèmes de type. Lorsque nous disons "un programme est de type-coffre-fort", ce que nous voulons dire est non qu'aucune erreur possible ne peut se produire. Nous ne pouvons que dire que les erreurs que le système de type nous promet de prévenir ne peut pas se produire.

Donc, les programmes peuvent avoir d'infiniment de nombreux comportements d'exécution. Parmi ceux-ci, ils sont infiniment utiles, mais aussi infiniment de nombreux sont "incorrects" (pour diverses définitions de "exactitude"). Un système de type statique nous permet de prouver qu'un certain ensemble fixe fini et fini de ceux infiniment de nombreux comportements d'exécution incorrects ne peuvent pas se produire.

La différence entre différents systèmes de type est essentiellement dans laquelle, combien de comportements d'exécution complexes pour ne pas se produire. Les systèmes de types faibles tels que Java ne peuvent prouver que des choses très fondamentales. Par exemple, Java peut prouver qu'une méthode qui est saisie comme renversement a String ne peut pas renvoyer un List. Mais, par exemple, il peut non Prouvez que la méthode ne vous retournera pas. Il ne peut pas non plus prouver que la méthode ne jette pas une exception. Et il ne peut pas prouver qu'il ne renvoie pas le fauxString - tout String _ va satisfaire le vérificateur de type. (Et bien sûr, même null _ _ va également le satisfaire.) Il existe même des choses très simples qui Java Je ne peux pas prouver, c'est pourquoi nous avons des exceptions telles que ArrayStoreException, ClassCastException, ou le favori de tout le monde, le NullPointerException.

Des systèmes de type plus puissants tels que AGDA peuvent également prouver que des choses telles que "renvoient la somme des deux arguments" ou "renvoie la version triée de la liste passée sous forme d'argument".

Maintenant, ce que signifient les concepteurs d'ELM par la déclaration qu'aucune exception d'exécution est que le système de type d'ELM peut prouver l'absence de comportements d'exécution (une partie importante des) éventuels que dans d'autres langues peut pas être prouvé à ne pas se produire et peut donc entraîner un comportement erroné au moment de l'exécution (qui signifie dans le meilleur cas une exception, dans un cas pire signifie un crash, et dans le pire des moyens de tous les moyens, aucune crash, aucune exception, et juste un résultat silencieux ).

Donc, ils sont non Dit "Nous ne mettons pas en œuvre des exceptions". Ils disent "des choses qui seraient des exceptions d'exécution dans des langues typiques que les programmeurs typiques de l'ELM auraient de l'expérience avec le système de type". Bien sûr, une personne venant d'Idris, Agda, Guru, Epigram, Isabelle/Hol, Coq ou des langues similaires verra ELM comme assez faible en comparaison. La déclaration est plus destinée à Typique Java, C♯, C++, Objective-C, PHP, Ecmascript, Python, Ruby, Perl, ... programmeurs.

10
Jörg W Mittag

L'ELM ne peut garantir aucune exception d'exécution pour la même raison C ne peut garantir aucune exception d'exécution: la langue ne prend pas en charge le concept d'exceptions.

ELM a un moyen de signaler les conditions d'erreur au moment de l'exécution, mais ce système n'est pas des exceptions, ce sont des "résultats". Une fonction qui peut échouer renvoie un "résultat" qui contient une valeur normale ou une erreur. Les elms sont fortement typés, il est donc explicite dans le système de type. Si une fonction renvoie toujours un entier, il a le type Int. Mais s'il renvoie un entier ou un échec, le type de retour est Result 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é):

view : String -> String 
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
        text "Not a valid number!"

    Ok age ->
        text "OK!"

La fonction toInt peut échouer si l'entrée n'est pas analyable, de sorte que son type de retour est Result String int. Pour obtenir la valeur entière réelle, vous devez "décompresser" par correspondance de motif, 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 est la "valeur par défaut". Les exceptions vont bouillir et résilier le programme par défaut et vous devez les attraper explicitement si vous souhaitez les gérer. Le résultat est l'autre solution - vous êtes obligé de les gérer par défaut, vous devez donc les transmettre d'explication jusqu'au sommet si vous voulez qu'ils résilient le programme. Il est facile de voir comment ce beavorage peut conduire à un code plus robuste.

4
JacquesB

Premièrement, veuillez noter que votre exemple d'exceptions "avaler" est en général une pratique terrible et complètement sans rapport avec des exceptions de temps d'exécution; Lorsque vous y réfléchissez, vous avez eu une erreur de temps d'exécution, mais vous avez choisi de le cacher et de ne rien faire. Cela entraînera souvent des bugs difficiles à comprendre.

Cette question pourrait être interprétée de n'importe quel type de façons, mais comme vous avez mentionné l'ALM dans les commentaires, le contexte est plus clair.

Elm est, entre autres choses, a typée statiquement langue de programmation. L'un des avantages de ce type de systèmes de type est que de nombreuses erreurs d'erreurs (mais pas toutes) sont capturées par le compilateur, avant que le programme soit réellement utilisé. Certaines types d'erreurs peuvent être codées dans des types (tels que ELM's Result et Task), au lieu d'être projetés comme des exceptions. C'est ce que signifie que les concepteurs d'ELM signifie: De nombreuses erreurs seront attrapées à la compilation au lieu de "l'heure d'exécution", et le compilateur vous obligera à traiter avec eux au lieu de les ignorer et d'espérer le meilleur. Il est clair que c'est un avantage: mieux que le programmeur prenait conscience d'un problème avant que l'utilisateur ne le fait.

Notez que lorsque vous n'utilisez pas d'exceptions, des erreurs sont codées de manière moins surprenante. De Documentation d'ELM :

Une des garanties d'ELM est que vous ne verrez pas d'erreurs d'exécution dans la pratique. Noredink utilise ELM en production pendant environ un an maintenant, et ils n'en ont toujours pas eu un! Comme toutes les garanties d'ELM, cela se résume à des choix de conception linguistique fondamentaux. Dans ce cas, nous sommes aidés par le fait que l'ELM traite des erreurs comme des données. (Avez-vous remarqué que nous faisons beaucoup de données ici?)

Les designers d'ELM sont un peu audacieux de réclamer "aucune exception de temps d'exécution" , bien qu'ils le qualifient de "dans la pratique". Ce qu'ils signifient probablement sont "des erreurs moins inattendues que si vous codez dans JavaScript".

2
Andres F.

ELM affirme:

Pas de runtime erreurs dans la pratique. Pas de null. Aucun indéfini n'est pas une fonction.

Mais vous posez des questions sur l'exécution exceptions. Il y a une différence.

En orme, rien ne renvoie un résultat inattendu. Vous ne pouvez pas écrire un programme valide dans l'ELM qui produit des erreurs d'exécution. Ainsi, vous n'avez pas besoin d'exceptions.

La question devrait donc être:

Quel est l'avantage d'avoir "aucune erreur d'exécution"?

Si vous pouvez écrire du code qui n'a jamais eu d'erreurs d'exécution, vos programmes ne se placeront jamais.

0
Héctor