When I run something like:
Prelude> cycle \"ab\"
I can see an infinite printing of \"ab\". To stop it I just use Ctrl+c
Great question! However, since How to abort execution in GHCI? already focuses on your second part, let's not repeat that here. Instead, let's focus on the first.
Why it so?
GHC optimizes loops aggressively. It optimizes them even further if there is no allocation that it's even a known bug:
19.2.1. Bugs in GHC
GHC’s runtime system implements cooperative multitasking, with context switching potentially occurring only when a program allocates. This means that programs that do not allocate may never context switch. This is especially true of programs using STM, which may deadlock after observing inconsistent state. See Trac #367 for further discussion. [emphasis mine]
If you are hit by this, you may want to compile the affected module with -fno-omit-yields (see -f*: platform-independent flags). This flag ensures that yield points are inserted at every function entrypoint (at the expense of a bit of performance).
If we check -fomit-yields, we find:
-fomit-yields
Default: yield points enabled
Tells GHC to omit heap checks when no allocation is being performed. While this improves binary sizes by about 5%, it also means that threads run in tight non-allocating loops will not get preempted in a timely fashion. If it is important to always be able to interrupt such threads, you should turn this optimization off. Consider also recompiling all libraries with this optimization turned off, if you need to guarantee interruptibility. [emphasis mine]
nub $ cycle "ab"
is a tight, non-allocating loop, although last $ repeat 1
is an even more obvious non-allocating example.
The "yield points enabled" is misleading: -fomit-yields
is enabled by default. As the standard library is compiled with -fomit-yields
, all functions in the standard library that lead to tight, non-allocating loops may show that behaviour in GHCi, as you never recompile them.
We can verify that with the following program:
-- Test.hs
myLast :: [a] -> Maybe a
myLast [x] = Just x
myLast (_:xs) = myLast xs
myLast _ = Nothing
main = print $ myLast $ repeat 1
We can use C-c to quit it if we run it in GHCi without compiling beforehand:
$ ghci Test.hs
[1 of 1] Compiling Main ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main <pressing C-c after a while>
Interrupted.
If we compile it and then rerun it in GHCi, it will hang:
$ 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>
Note that you need -dynamic
if you don't use Windows, as otherwise GHCi will recompile the source file. However, if we use -fno-omit-yield
, we suddenly can quit again (in Windows).
We can verify that again with another small snippet:
Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted
As ghci
doesn't use any optimizations, it also doesn't use -fomit-yield
(and therefore has -fno-omit-yield
enabled). Our new variant of last
doesn't yield the same behaviour as Prelude.last
as it isn't compiled with fomit-yield
.
Now that we know why this happens, we know that we will experience that behaviour throughout the standard library, as the standard library is compiled with -fomit-yield
.