2010-01-26 5 views
14

Comment puis-je obtenir plus d'informations sur l'origine d'une erreur Haskell? Par exemple, hier, je travaillais sur un programme Haskell qui analyse un fichier d'entrée, transforme les données et imprime les informations de reporting.Suivi des erreurs dans Haskell

À un moment donné, j'ai couru "principal" et je revins

*** Prelude.read: parse error 

sans autre information. Heureusement, je savais que j'appelais lire en un seul endroit et a été en mesure de le réparer, mais pour l'avenir:

  • Est-il possible d'obtenir un backtrace ou un numéro de ligne pour des erreurs comme celles-ci?
  • Est-il possible d'obtenir les données réelles qui ont déclenché l'erreur, c'est-à-dire la chaîne qui a provoqué l'erreur d'analyse?

Merci!

Édition Utilisation de GHC.

+0

Vous feriez mieux d'éviter complètement les fonctions partielles. Utilisez 'Safe.readMay' à la place. –

+0

voir [ici] (http://stackoverflow.com/questions/8595077/how-can-i-get-the-position-where-error-was-called) pour une meilleure solution – Simon

Répondre

5

vous pouvez obtenir la chaîne qui a causé l'erreur d'analyse en important Debug.Trace et en changeant vous appel

 
import Debug.Trace (trace) 

--change 
myRead s = read s 
--to 
myRead s = trace s (read s) 
--or 
myRead s = trace (take 100 s) (read s) 
+0

Je trouve '\ s-> fst $ head $ lit s ++ [erreur $ "Impossible de lire:" ++ show s] 'plus utile car il ne spamme pas quand la lecture est réussie. – Rotsor

2

Vous ne nous avez pas dit quel compilateur vous utilisez. Si vous utilisez GHC, alors vous devriez jeter un oeil à la GHCi Debugger.

Le traçage de pile dans Haskell n'est pas trivial, à cause de sa paresse. Néanmoins, le débogueur mentionné ci-dessus fournit quelques outils (voir la section 2.5.5 «Traçage et historique» dans l'URL ci-dessus).

3

En général, il vous appartient de gérer les erreurs de manière à ce qu'il y ait suffisamment de contexte pour que vous puissiez déboguer la cause. La paresse de Haskell rend les traces de pile difficiles à implémenter, car la pile d'appels peut ne plus exister au moment où l'erreur se produit. Un moyen simple de gérer les erreurs est d'utiliser le type Either qui permet de renvoyer une valeur quand les choses vont bien, ou un contexte (message d'erreur, la chaîne d'entrée, ...) en cas d'erreur. Enfin, dans votre cas spécifique, read émet une exception, vous devrez donc l'attraper puis gérer l'erreur dans le code appelant (consultez le package Control.Exception).

1

Vous pouvez envisager d'utiliser un read monadique comme dans "Practical Haskell: shell scripting with error handling and privilege separation" par des collègues de l'utilisateur dons:

La première étape consiste à remplacer read avec une version levée dans une monade d'erreur générique, MonadError:

readM :: (MonadError String m, Read a) => String -> m a 
readM s | [x] <- parse = return x 
     | otherwise = throwError $ "Failed parse: " ++ show s 
    where 
     parse = [x | (x,t) <- reads s] 
7

Si vous pouvez exécuter le code dans ghci, le débogueur peut faire tout ce que vous voulez.Voici un programme qui soulève une exception

foo s i 
    | i == 57 = read s 
    | otherwise = i 
main = mapM_ (print . foo "") [1..100] 

charge maintenant dans ghci et utilisez le débogueur, comme indiqué ici: http://www.haskell.org/ghc/docs/latest/html/users_guide/ghci-debugger.html#ghci-debugger-exceptions

> ghci test.hs 
*Main> :set -fbreak-on-error 
*Main> :trace main 
1 
2 
... snipped 3 through 55 ... 
56 
Stopped at <exception thrown> 
_exception :: e = _ 
[<exception thrown>] *Main> :back 
Logged breakpoint at test.hs:2:15-20 
_result :: a 
s :: String 
[-1: test.hs:2:15-20] *Main> :list 
1 foo s i 
2 | i == 57 = **read s** 
3 | otherwise = i 
[-1: test.hs:2:15-20] *Main> s 
"" 
[-1: test.hs:2:15-20] *Main> 

Il vous permet un pas autour de l'histoire de l'évaluation, met en évidence la réelle Expression qui a déclenché l'exception (gras plutôt que celle d'un terminal) et vous permet d'inspecter les variables locales.

Une autre option est de recompiler avec le profilage et des drapeaux pour baliser les centres de coûts appropriés, et exécuter avec l'option de profilage -xc qui imprime la pile de centre de coûts sur les exceptions non rattrapées http://www.haskell.org/ghc/docs/latest/html/users_guide/prof-time-options.html

> ghc -prof -auto-all test.hs 
> ./test +RTS -cs 
1 
2 
... snipped 3 through 55 ... 
56 
*** Exception (reporting due to +RTS -xc): (THUNK_2_0), stack trace: 
    Main.foo, 
    called from Main.main, 
    called from Main.CAF 
    --> evaluated by: Main.main, 
    called from Main.CAF 
test: Prelude.read: no parse 

La raison pour cela est Un peu difficile est décrit un peu plus tôt sur la page de débogage http://www.haskell.org/ghc/docs/latest/html/users_guide/ghci-debugger.html#tracing Fondamentalement, l'exécution efficace de Haskell n'utilise pas quelque chose ressemblant à une pile d'appels normale, donc pour obtenir ce genre d'information sur une exception, vous devez exécuter dans un mode spécial (débogage ou profilage) qui garde cette sorte d'information.