Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I stop infinite evaluation in GHCi?

Tags:

haskell

ghci

When I run something like:

Prelude> cycle "ab"

I can see an infinite printing of "ab". To stop it I just use Ctrl+c. And it works.

When I run:

Prelude Data.List> nub $ cycle "ab"

I am not able to stop it.

Question:

  • Why it so?
  • How can I stop this operation?

Update:

 Ubuntu: version 18.10  
 GHCi:   version 8.2.2
like image 214
mkUltra Avatar asked Mar 15 '19 18:03

mkUltra


People also ask

How do I interrupt GHCi?

Quits GHCi. You can also quit by typing control-D at the prompt. Attempts to reload the current target set (see :load ) if any of the modules in the set, or any dependent module, has changed.

How do I stop Haskell running?

So the proper way to close haskell-shell is to hit Ctrl+D .

What is Prelude in GHCi?

Prelude is a module that contains a small set of standard definitions and is included automatically into all Haskell modules.

How do I run a Haskell file in GHCi?

Open a command window and navigate to the directory where you want to keep your Haskell source files. Run Haskell by typing ghci or ghci MyFile. hs. (The "i" in "GHCi" stands for "interactive", as opposed to compiling and producing an executable file.)


1 Answers

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.

like image 122
Zeta Avatar answered Oct 16 '22 15:10

Zeta