Comment fonctionnent les exceptions dans Haskell?

86

Dans GHCi:

Prelude> error (error "")
*** Exception: 
Prelude> (error . error) ""
*** Exception: *** Exception: 

Pourquoi le premier n'est-il pas une exception imbriquée?

Daniel Wagner
la source
9
C'est une transformation que GHC est autorisée à faire: "Je suis le compilateur qui opère par lui-même, et tous les _ | _ me ressemblent". Demandez-vous les détails d'implémentation qui font que ces deux lignes se compilent différemment?
shachaf
3
errorest un mécanisme spécial et pas vraiment d'exception. Pour de vraies exceptions capturables, voir Errormonade.
Cat Plus Plus
1
Notez, par exemple, que cela (\f g x -> f (g x)) error error ""se comporte différemment de (.) error error "", même si cette fonction est équivalente à (.). Cela a peut-être à voir avec les indicateurs d'optimisation avec lesquels Prelude a été compilé.
shachaf
5
Aussi iterate error "" !! net le génial fix error.
Vitus
9
Je fais toujours semblant error = erroret programme en conséquence.
Gabriel Gonzalez

Réponses:

101

La réponse est que c'est la sémantique (quelque peu surprenante) des exceptions imprécises

Lorsqu'on peut montrer que le code pur s'évalue à un ensemble de valeurs exceptionnelles (c'est-à-dire la valeur de errorou undefined, et explicitement pas le type d'exceptions générées dans IO ), alors le langage permet de renvoyer n'importe quelle valeur de cet ensemble. Les valeurs exceptionnelles dans Haskell ressemblent plus à NaNdu code à virgule flottante qu'à des exceptions basées sur le flux de contrôle dans les langages impératifs.

Un casse-tête occasionnel, même pour les Haskellers avancés, est un cas tel que:

 case x of
   1 -> error "One"
   _ -> error "Not one"

Puisque le code évalue un ensemble d'exceptions, GHC est libre d'en choisir une. Avec les optimisations activées, vous constaterez peut-être que cela équivaut toujours à "Pas un".

Pourquoi faisons-nous cela? Parce que sinon, nous limiterions trop l'ordre d'évaluation du langage, par exemple, nous devrons fixer un résultat déterministe pour:

 f (error "a") (error "b")

par exemple, en exigeant qu'il soit évalué de gauche à droite si des valeurs d'erreur sont présentes. Très peu Haskelly!

Puisque nous ne voulons pas paralyser les optimisations qui peuvent être faites sur notre code juste pour la prise en charge error, la solution est de spécifier que le résultat est un choix non déterministe parmi l'ensemble des valeurs exceptionnelles: des exceptions imprécises! D'une certaine manière, toutes les exceptions sont renvoyées et une est choisie.

Normalement, vous ne vous souciez pas - une exception est une exception - sauf si vous vous souciez de la chaîne à l'intérieur de l'exception, auquel cas l'utilisation de errorpour déboguer est très déroutante.


Références: Une sémantique pour les exceptions imprécises , Simon Peyton Jones, Alastair Reid, Tony Hoare, Simon Marlow, Fergus Henderson. Conception et mise en œuvre des langages de programmation Proc (PLDI'99), Atlanta. ( PDF )

Don Stewart
la source
2
Je comprends que GHC a choisi l'une des exceptions qui pourraient être rencontrées. Mais dans votre exemple "cas", l'exception "Pas un" ne peut pas être rencontrée pour une entrée de 1, donc je classerais toujours cela comme un bogue.
Peaker
8
@Peaker dead code elim - l'optimiseur n'a pas besoin de regarder x pour voir qu'une erreur est le résultat, toutes les branches produisent la "même" valeur, donc il peut ignorer entièrement la valeur d'entrée. Pas un bug, sous des exceptions imprécises!
Don Stewart
1
@lpsmith: Je pense que tous les types d'exceptions sont imprécis (lorsqu'ils sont lancés avec throw) et avec lesquels vous pouvez lever une exception de manière déterministe throwIO.
FunctorSalad
4
@Peaker Je pense que vous avez raison. Je ne pense pas que la ghc devrait optimiser cette expression si elle veut jouer selon les règles énoncées dans le document sur les exceptions imprécises.
augustss
3
Qu'est - ce que le papier d'exceptions imprécises ne semble dire que si le scrutinee de cas est une valeur d'erreur, alors les valeurs d'erreur des branches peuvent être retournés. Alors case error "banana" of (x:xs) -> error "bonobo"peut vous donner * Exception: bonobo.
Ben Millwood