Comment “déboguer” Haskell avec printfs?

venant de la communauté Ocaml, j’essaie d’apprendre un peu de Haskell. La transition se passe plutôt bien mais je suis un peu confus avec le débogage. J’avais l’habitude de mettre beaucoup de “printf” dans mon code ocaml, d’inspecter certaines valeurs intermédiaires, ou comme indicateur pour voir où le calcul avait échoué exactement.

Étant donné que printf est une action IO , dois-je lever tout mon code haskell dans la monade IO pour pouvoir effectuer ce type de débogage? Ou existe-t-il un meilleur moyen de le faire (je ne veux vraiment pas le faire à la main si cela peut être évité)

Je trouve également la fonction trace : http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends qui semble exactement ce que je veux, mais je ne comprends pas son type: il n’ya pas d’ OI quelque part! Quelqu’un peut-il m’expliquer le comportement de la fonction trace?

trace est la méthode la plus facile à utiliser pour le débogage. Ce n’est pas dans IO exactement pour la raison que vous avez indiquée: pas besoin de lever votre code dans la monade IO . C’est implémenté comme ça

 trace :: Ssortingng -> a -> a trace ssortingng expr = unsafePerformIO $ do putTraceMsg ssortingng return expr 

Il y a donc IO dans les coulisses, mais unsafePerformIO est utilisé pour y échapper. C’est une fonction qui casse potentiellement la transparence référentielle que vous pouvez deviner en regardant son type IO a -> a et aussi son nom.

trace est simplement rendue impure. Le but de la monade IO est de préserver la pureté (pas de IO inaperçu par le système de type) et de définir l’ordre d’exécution des instructions, qui serait pratiquement indéfini par une évaluation paresseuse.

Cependant, à vos risques et périls, vous pouvez néanmoins regrouper des IO a -> a et des performances imparfaites. Ceci est un hack et bien sûr “souffre” d’une évaluation paresseuse, mais c’est ce que la trace fait simplement pour le débogage.

Néanmoins, vous devriez probablement utiliser d’autres moyens pour le débogage:

1) Réduire le besoin de déboguer les valeurs intermédiaires

  • Écrivez de petites fonctions réutilisables, claires et génériques dont la correction est évidente.
  • Combinez les pièces correctes pour obtenir de plus grandes pièces correctes.
  • Rédiger des tests ou essayer des pièces de manière interactive

2)

  • Utiliser des points d’arrêt, etc. (débogage basé sur le compilateur)

3)

  • Utilisez des monades génériques. Si votre code est monadique néanmoins, écrivez-le indépendamment d’une monade concrète. Utilisez le type M a = ... au lieu des IO ... simples IO ... Vous pouvez ensuite facilement combiner des monades à travers des transformateurs et y placer une monade de débogage. Même si vous n’avez plus besoin de monades, vous pouvez simplement insérer l’ Identity a pour les valeurs pures.

Pour ce que ça vaut, il y a en fait deux sortes de “débogage” en cause ici:

  • Consigner les valeurs intermédiaires, telles que la valeur d’une sous-expression particulière à chaque appel dans une fonction récursive
  • Inspection du comportement d’exécution de l’évaluation d’une expression

Dans un langage impératif ssortingct, ils coïncident généralement. En Haskell, ils ne le font souvent pas:

  • L’enregistrement de valeurs intermédiaires peut modifier le comportement d’exécution, par exemple en imposant l’évaluation de termes qui seraient autrement ignorés.
  • Le processus de calcul proprement dit peut différer considérablement de la structure apparente d’une expression en raison de la paresse et des sous-expressions partagées.

Si vous voulez simplement garder un journal des valeurs intermédiaires, il y a plusieurs façons de le faire – par exemple, plutôt que de tout rentrer dans IO , une simple monade Writer suffira, cela équivaut à faire que les fonctions renvoient un double de leur résultat réel et une valeur d’accumulateur (une sorte de liste, généralement).

Il n’est généralement pas nécessaire de tout mettre dans la monade, mais uniquement les fonctions qui doivent écrire dans la valeur “log” – par exemple, vous pouvez ne prendre en compte que les sous-expressions pouvant nécessiter une journalisation, en laissant la logique principale pure. puis remonter le calcul global en combinant des fonctions pures et des calculs de journalisation de la manière habituelle avec fmap s et whatnot. Gardez à l’esprit que Writer est en quelque sorte une excuse désolante pour une monade: sans aucun moyen de lire le journal, écrivez seulement, chaque calcul est logiquement indépendant de son contexte, ce qui facilite le jonglage des choses.

Mais dans certains cas, même si c’est exagéré – pour de nombreuses fonctions pures, il suffit de déplacer des sous-expressions vers le niveau supérieur et d’essayer des choses dans la REPL.

Si vous voulez inspecter le comportement d’exécution du code pur, par exemple, pour comprendre pourquoi une sous-expression diverge – il n’y a en général aucun moyen de le faire à partir d’autres codes purs – en fait, c’est essentiellement la définition de la pureté. Donc, dans ce cas, vous n’avez d’autre choix que d’utiliser des outils qui existent “en dehors” du langage pur: soit des fonctions impures telles que unsafePerformPrintfDebugging –errr, soit trace – ou un environnement d’exécution modifié, tel que le débogueur GHCi.

trace également tendance à surévaluer son argument en faveur de l’impression, perdant beaucoup des avantages de la paresse dans le processus.

Si vous pouvez attendre que le programme soit terminé avant d’étudier la sortie, emstackr une moniteuse Writer est l’approche classique de l’implémentation d’un enregistreur. Je l’utilise ici pour retourner un jeu de résultats à partir d’un code HDBC impur.

Eh bien, étant donné que Haskell entier est construit autour du principe de l’évaluation paresseuse (de sorte que l’ordre des calculs est en fait non déterministe), l’utilisation de printf n’y a pas beaucoup de sens.

Si REPL + inspecte les valeurs résultantes n’est vraiment pas suffisant pour votre débogage, tout inclure dans IO est le seul choix (mais ce n’est pas la bonne façon de programmer Haskell).