Friday, September 22, 2006

Debugging in Haskell

Some few days ago, an innocent query sparkled a fiery discussion in the Haskell mailing list, this time about the virtues and vices of debugging.

At the time I did put up a quick survey page in the wiki about the various debugging options available in Haskell (including the ghci debugger, of course!), but I didn't think about mentioning it here. A partial rip-off of that page follows.

Printf and friends
The simplest approach is to use Debug.Trace.trace:
trace :: String -> a -> a
When called, trace outputs the string in its first argument, before returning the second argument as its result.

A common idiom to trace a function is:
myfun a b | trace ("myfun " ++ show a ++ " " ++ show b) False = undefined
myfun a b = ...
The advantage is that disabling and enabling the trace takes only one line comment.
You must keep in mind that due to lazy evaluation your traces will only print if the value they wrap is ever demanded.

A more powerful alternative for this approach is Hood. Even if it hasn't been updated in some time, Hood works perfectly with the current ghc distribution. Even more, Hugs has it already integrated, see the manual page. Add an import Observe and start inserting observations in your code.
For instance:
import Hugs.Observe

f' = observe "Informative name for f" f
f x = if odd x then x*2 else 0
And then in hugs:
Main> map f' [1..5]
[2,0,6,0,10]

>>>>>>> Observations <<<<<<> 10
, \ 4 -> 0
, \ 3 -> 6
, \ 2 -> 0
, \ 1 -> 2
}
The evaluation outputs a report of all the invocations of f and their result.

I have a handy bogus Hugs.Observe module with no-ops for the observations so that I don't need to remove them manually, expecting that the compiler will optimize them away.

Dynamic breakpoints in GHCi
Finally, the GHCi Debugger project aims to bring dynamic breakpoints and intermediate values observation to GHCi in a near future. Right now the tool is only available from the site as a modified version of GHC, so unfortunately you will have to compile it yourself if you want to have it.

This tool allows to set breakpoints in your code, directly from the GHCi command prompt. An example session:
*main:Main> :break add Main 2
Breakpoint set at (2,15)
*main:Main> qsort [10,9..1]
Local bindings in scope:
x :: a, xs :: [a], left :: [a], right :: [a]

qsort2.hs:2:15-46> :sprint x
x = _
qsort2.hs:2:15-46> x
This is an untyped, unevaluated computation. You can use seq to
force its evaluation and then :print to recover its type
qsort2.hs:2:15-46> seq x ()
()
qsort2.hs:2:15-46> :p x
x - 10
Once a breakpoint is hit, you can explore the bindings in scope, as well as to evaluate any haskell expression, as you would do in a normal GHCi prompt. The ':print' command can be very useful to explore the lazyness of your code.

2 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.