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
2)
3)
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:
Dans un langage impératif ssortingct, ils coïncident généralement. En Haskell, ils ne le font souvent pas:
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).