16 votes

Comment puis-je arrêter l'évaluation infinie dans GHCi ?

Quand j'exécute quelque chose comme :

Prelude> cycle "ab"

Je peux voir une impression infinie de "ab". Pour l'arrêter, j'utilise simplement Ctrl + c . Et ça marche.

Quand je cours :

Prelude Data.List> nub $ cycle "ab"

Je ne suis pas capable de l'arrêter.

Pregunta:

  • Pourquoi ?
  • Comment puis-je arrêter cette opération ?

Mise à jour :

 Ubuntu: version 18.10  
 GHCi:   version 8.2.2

12voto

Zeta Points 34033

Grande question ! Cependant, puisque Comment interrompre l'exécution dans GHCI ? se concentre déjà sur votre deuxième partie, ne le répétons pas ici. Au lieu de cela, concentrons-nous sur la première.

Pourquoi ?

GHC optimise les boucles de manière agressive. Il les optimise encore plus s'il n'y a pas d'allocation. que c'est même un bug connu :

19.2.1. Bogues dans GHC

  • Le système d'exécution de GHC implémente le multitâche coopératif, le changement de contexte ne se produisant potentiellement que lorsqu'un programme alloue. Cela signifie que les programmes qui n'allouent pas peuvent ne jamais changer de contexte. Cela est particulièrement vrai pour les programmes utilisant STM, qui peuvent se bloquer après avoir observé un état incohérent. Voir Trac #367 pour une discussion plus approfondie. [souligné par moi]

    Si vous êtes touché par ce problème, vous pouvez compiler le module concerné avec -fno-omit-yields (voir -f* : drapeaux indépendants de la plateforme ). Ce drapeau garantit que les points de rendement sont insérés à chaque point d'entrée de la fonction (au détriment d'un peu de performance).

Si nous vérifions -fomit-yields nous trouvons :

-fomit-yields

Défaut : points de rendement activés

Indique à GHC d'omettre les vérifications du tas lorsqu'aucune allocation n'est effectuée. Bien que cela améliore la taille des binaires d'environ 5 %, cela signifie également que la vérification du tas n'est pas nécessaire. que les threads exécutés dans des boucles serrées sans allocation ne seront pas préemptés. de manière opportune. S'il est important de toujours pouvoir interrompre ces threads, vous devez désactiver cette optimisation. Pensez également à recompilation de toutes les bibliothèques avec cette optimisation désactivée, si vous devez garantir l'interruptibilité. [souligné par moi]

nub $ cycle "ab" est une boucle serrée, non allouante, bien que last $ repeat 1 est un exemple encore plus évident de non-allocation.

L'expression "points de rendement activés" est trompeuse : -fomit-yields est activé par défaut. Comme la bibliothèque standard est compilée avec -fomit-yields , toutes les fonctions de la bibliothèque standard qui conduisent à des boucles serrées et non allouées. peuvent présenter ce comportement dans GHCi, car vous ne les recompilez jamais.

Nous pouvons le vérifier avec le programme suivant :

-- Test.hs
myLast :: [a] -> Maybe a
myLast [x]    = Just x
myLast (_:xs) = myLast xs
myLast _      = Nothing

main = print $ myLast $ repeat 1

Nous pouvons utiliser C-c pour le quitter si nous l'exécutons dans GHCi sans compiler au préalable :

$ ghci Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main            <pressing C-c after a while>
Interrupted.

Si nous le compilons et le réexécutons ensuite dans GHCi, il se bloquera :

$ ghc Test.hs
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test.exe ...

$ ghci Test.hs
Ok, one module loaded.
*Main> :main
<hangs indefinitely>

Notez que vous devez -dynamic si vous n'utilisez pas Windows, car sinon GHCi recompilera le fichier source. Cependant, si nous utilisons -fno-omit-yield on peut soudainement quitter à nouveau (dans Windows).

Nous pouvons le vérifier à nouveau avec un autre petit extrait :

Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted

Como ghci n'utilise pas d'optimisations, il n'utilise pas non plus de -fomit-yield (et a donc -fno-omit-yield activé). Notre nouvelle variante de last ne donne pas le même comportement que Prelude.last car il n'est pas compilé avec fomit-yield .

Maintenant que nous savons pourquoi si cela se produit, nous savons que nous aurons ce comportement dans toute la bibliothèque standard, puisque celle-ci est compilée avec -fomit-yield .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X