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
.