Get front row seat and watch the development of micro-isv. We make cool product that solves all your problems...

logo

Friends
         
Haskell Prelude Exception Debugging
2009-03-10

One of the 'interesting' features of Haskell's lazy evaluation is that traditional stack traces are not available. This makes debugging more tricky.

One common problem is to get an error message from the standard library that says '*** Exception: Prelude.read: no parse' or '*** Exception: Prelude.tail: empty list'

What you need to do is to fire up ghci and tell it to drop into a debugger when an exception that will propagate to the top level is about to be thrown, using the '-fbreak-on-error' flag, then :trace the execution of your function. For example, here is our application:

# cat app.hs
complicatedInnerFunction x = read x :: Int
toplevelApp = complicatedInnerFunction

Imagine now if you will about 50 thousand lines of boilerplate between the 'read' code that solves the problem and the interface. If you have coded in Java before, this is much easier. Lets try it out:

# ghci /tmp/app.hs 
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( /tmp/app.hs, interpreted )
Ok, modules loaded: Main.
*Main> toplevelApp "ff"
*** Exception: Prelude.read: no parse

Not really any help. Now to tell it break when an exception will propagate to the top level:

*Main> :set -fbreak-on-error

*Main> :trace toplevelApp "ff"
Stopped at <exception thrown>
_exception :: e = GHC.IOBase.ErrorCall ['P','r','e',....]

Now we can find out how it got here:

[<exception thrown>] *Main> :history
-1  : complicatedInnerFunction (/tmp/app.hs:2:29-41)
-2  : complicatedInnerFunction (/tmp/app.hs:2:0-41)
<end of history>

That's given us a line number that actually caused the problem, which in many cases is enough to track the problem down, or we can actually use the debugger:

[<exception thrown>] *Main> :back

Logged breakpoint at /tmp/app.hs:2:29-41
_result :: Int
x :: String

This has bound the variables into our scope, so we can inspect them

[-1: /tmp/app.hs:2:29-41] *Main> print x
"ff"
[-1: /tmp/app.hs:2:29-41] *Main> 
length x
2

Full chapter and verse is on the GHC Docs