Question

Je voudrais savoir comment le débogage est réalisé dans un langage fonctionnel paresseux.
Pouvez-vous utiliser des points d'arrêt, des déclarations d'impression et les techniques traditionnelles? Est-ce même une bonne idée?
Je crois comprendre que pur la programmation fonctionnelle ne permet pas d'effets secondaires, à l'exception des monades.
Ordre d'exécution est également pas garantie.
Auriez-vous de programmer une monade pour chaque section de code que vous voulez tester? Je voudrais un aperçu de cette question de quelqu'un plus expérimenté dans ce domaine.

Était-ce utile?

La solution

Rien ne vous empêche d'utiliser un programme dans des points d'arrêt fonctionnel évalué paresseusement. La différence d'évaluation est désireux quand le programme s'arrêtera au point d'arrêt et ce que la trace ressemblera. Le programme s'arrête lorsque l'expression d'arrêt est défini sur est effectivement réduite (évidemment).

Au lieu de la trace de la pile que vous avez l'habitude de vous obtenir les réductions qui ont conduit à la réduction de l'expression avec le point d'arrêt sur elle.

Petit exemple stupide. Vous avez ce programme Haskell.

add_two x = 2 + x

times_two x = 2 * x

foo = times_two (add_two 42)

Et vous mettez un point d'arrêt sur la première ligne (add_two), puis évaluer foo. Lorsque le programme cesse sur le point d'arrêt, dans une langue que vous attendez désireux d'avoir une trace comme

add_two
foo

et times_two n'a pas encore commencé à évaluer, mais dans le débogueur GHCi vous obtenez

-1  : foo (debug.hs:5:17-26)
-2  : times_two (debug.hs:3:14-18)
-3  : times_two (debug.hs:3:0-18)
-4  : foo (debug.hs:5:6-27)
<end of history>

qui est la liste des réductions qui ont abouti à la réduction de l'expression que vous mettez le point d'arrêt sur. Notez qu'il ressemble times_two « appelé » foo même si elle ne le fait pas explicitement. Vous pouvez voir ce que l'évaluation des 2 * x dans times_two (-2) ne forcer l'évaluation des (add_two 42) (-1) de la ligne de foo. De là, vous pouvez effectuer une étape comme dans un débogueur impératif (effectuer la réduction suivante).

Une autre différence de débogage dans une langue avide est que les variables peuvent être thunks pas encore évalués. Par exemple, à l'étape -2 dans la trace ci-dessus et inspecter x, vous trouverez qu'il est encore un non évalué thunk (indiqué par des crochets dans GHCi).

Pour plus d'informations beaucoup plus détaillées et des exemples (comment l'étape à travers la trace, inspectez les valeurs, ...), voir la section Debugger gHCi dans le manuel GHC. Il y a aussi Leksah IDE que je ne l'ai pas encore utilisé comme je suis un VIM et utilisateur du terminal, mais il a un interface graphique pour le débogueur GHCi selon le manuel.

Vous avez également demandé des déclarations d'impression. Seulement avec des fonctions pures, ce n'est pas si facilement possible une déclaration d'impression devrait être dans le monade IO. Donc, vous avez une fonction pure

foo :: Int -> Int

et que vous souhaitez ajouter une instruction trace, l'impression renvoyait une action dans la monade IO et donc vous auriez à régler la signature de la fonction que vous souhaitez mettre cette déclaration de trace dans, et les signatures des fonctions appeler ...

Ce n'est pas une bonne idée. Donc, vous avez besoin d'un moyen de briser la pureté pour obtenir des états de trace. Dans Haskell, cela peut être fait avec unsafePerformIO. Il y a le module Debug.Trace qui a déjà une fonction

trace :: String -> a -> a

qui délivre en sortie la chaîne et renvoie le second paramètre. Il serait impossible d'écrire en fonction pur (bien, si vous avez l'intention de la chaîne vraiment sortie, qui est). Il utilise unsafePerformIO sous le capot. Vous pouvez mettre cela en une pure fonction pour produire une impression de trace.

  

Auriez-vous programmer une monade pour chaque section de code que vous voulez tester?

Je vous suggère plutôt le contraire, faire autant de fonctions pures que possible (je suppose ici que vous voulez parler de la monade IO pour l'impression, monades ne sont pas nécessairement impur). Lazy évaluation vous permet de séparer le code IO à partir du code de traitement très proprement.

Si les techniques de débogage impératives sont une bonne idée ou non dépend de la situation (comme d'habitude). Je trouve des tests avec QuickCheck / SmallCheck beaucoup plus utiles que les tests unitaires dans les langages impératifs, donc j'aller dans cette voie d'abord pour éviter autant que possible le débogage. propriétés QuickCheck agissentfaire sivement belles spécifications de fonction concis (beaucoup de code de test dans les langues impératives ressemble juste un autre blob de code pour moi).

Une astuce pour éviter beaucoup de débogage est de décomposer la fonction en plusieurs sous-fonctions plus petites et tester le plus grand nombre possible d'entre eux. Cela peut être un peu unusal en venant de la programmation impérative, mais il est une bonne habitude, peu importe la langue que vous utilisez.

Et puis, le débogage! = Test et si quelque chose va mal quelque part, et des traces peuvent breakpoints vous aider.

Autres conseils

Je ne pense pas que ce sujet peut être traité dans un court espace. S'il vous plaît lire les journaux disponibles sur les liens suivants:

  1. Théorie de traçage des programmes fonctionnels purs .
  2. Les publications Haskell Tracer .
  3. Haskell Debugging Technologies .

Je ne l'ai jamais fouillé dans quoi que ce soit terriblement compliqué en Haskell, mais le fait que les effets secondaires sont pratiquement disparu a éliminé la plupart de la nécessité pour le débogage. fonctions pures sont extrêmement simples à tester et vérifier sans un débogueur.

D'autre part, je l'ai fait l'expérience une ou deux fois je avais besoin pour déboguer quelque chose dans un monade, auquel cas je l'ai déjà été capable d'imprimer / log / whatever.

Au moins pour les petits programmes ou systèmes, le débogage va sorte de la fenêtre. typage fort et vérification de type statique vraiment éliminer les bugs plus traditionnels que vous trouverez dans la programmation procédurale. La plupart des insectes, le cas échéant, des bugs logiques (appelés les mauvaises fonctions, erreur mathématique, etc.) - très facile à tester de manière interactive

A partir de l'expérience avec Clojure (qui est paresseux, fonctionnelle, et encourage, mais ne pas respecter la pureté):

  • Vous pouvez définir des points d'arrêt comme avec tout autre langue. Cependant, becuase de l'évaluation paresseuse, ceux-ci risquent de ne pas obtenir appelé immédiatement, mais seront touchés dès que l'évaluation de la structure paresseuse est forcée.

  • Dans les langages fonctionnels paresseux qui permettent des effets secondaires (Clojure inclus) vous pouvez insérer printlns et d'autres débogage de journalisation relativement facilement. Personnellement, je trouve ces très utile. Vous devez être prudent quand ceux-ci s'appellent à cause de la paresse, mais si vous ne voyez pas la sortie du tout il peut être une indication que votre code n'est pas en cours d'évaluation en raison de la paresse .....

Cela dit tout ce qui précède, je ne l'ai jamais jusqu'à présent nécessaire d'avoir recours au débogueur. Souvent, quelques tests simples (peut-être sur le REPL) suffisent pour vérifier que le code fonctionnel fonctionne correctement, et si ceux-ci échouent, il est généralement tout à fait évident ce qui va mal.

Permettez-moi d'annoncer un outil de mes propres problèmes de paresse de débogage. Il m'a aidé à résoudre en une heure une fuite de mémoire liée à la paresse qui je l'ai déjà passé 2 jours de débogage.

http://www.haskell.org/pipermail/ haskell-café / 2012-Janvier / 098847.html

http://hackage.haskell.org/package/htrace

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top