Haskell/Debugging

Debug prints with Debug.TraceEdit

One common way to debug programs in general is to use debug prints. In imperative languages we can just sprinkle the code with print statements that output debug information (e.g. value of a particular variable, or some human-readable message) to standard output or some as log file. In Haskell, however, it is not possible to output any information if we are not in IO monad; and therefore this trivial debug print technique can't be used with pure functions without rewriting them to use the IO monad.

To deal with this problem, the standard library provides the Debug.Trace. One of the functions this module exports is called trace, which provides a convenient way to attach debug print statements anywhere in a program. For instance, this program prints every argument passed to fib that is not equal to 0 or 1:

module Main where
import Debug.Trace
 
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = trace ("n: " ++ show n) $ fib (n - 1) + fib (n - 2)
 
main = putStrLn $ "fib 4: " ++ show (fib 4)

Below is the resulting output:

n: 4
n: 3
n: 2
n: 2
fib 4: 3

Also trace makes possible to trace execution steps of program; that is, which function is called first, second, etc. To do so, we annotate parts of functions we are interested in, like this:

module Main where
import Debug.Trace
 
factorial :: Int -> Int
factorial n | n == 0    = trace ("branch 1") 1
            | otherwise = trace ("branch 2") $ n * (factorial $ n - 1)
 
main = do
    putStrLn $ "factorial 6: " ++ show (factorial 6)

When a program annotated in such way is run, it will print the debug strings in the same order the annotated statements were executed. That output might help to locate errors in case of missing statements or similar things.

Some extra adviceEdit

As demonstrated above, trace can be used outside of the IO monad; and indeed its type signature...

trace :: String -> a -> a

...indicates that it is a pure function. That, however, looks mystifying - surely trace is doing IO while printing useful messages; how can it be pure then? In fact, trace uses a dirty trick of sorts to circumvent the separation between the IO monad and pure code. That is reflected in the following disclaimer, found in the documentation for trace:

The trace function should only be used for debugging, or for monitoring execution. The function is not referentially transparent: its type indicates that it is a pure function but it has the side effect of outputting the trace message.

Naturally, there is no reason to worry provided you follow the advice above and only use trace for debugging, and not for regular functionality.

Also, a common mistake in using trace is, while trying to fit the debug traces into an existing function, accidentally including the value being evaluated in the message to be printed by trace; i.e. don't do anything like this:

let foo = trace ("foo = " ++ show foo) $ bar
in  baz

This leads to infinite recursion, as trace message will be evaluated before bar expression which will lead to evaluation of foo in terms of trace message and bar again and trace message will be evaluated before bar and so forth to infinity. Instead of show foo, the trace message rather have show bar:

let foo = trace ("foo = " ++ show bar) $ bar
in  baz

Useful idiomsEdit

A helper function that incorporates show can be convenient:

traceThis :: (Show a) => a -> a
traceThis x = trace (show x) x

In a similar vein, Debug.Trace defines a traceShow function, that "prints" its first argument and evaluates to the second one:

traceShow :: (Show a) => a -> b -> b
traceShow = trace . show

Finally, a function debug like this one may prove handy as well:

debug = flip trace

This will allow you to write code like...

main = (1 + 2) `debug` "adding"

... making it easier to comment/uncomment debugging statements.

Incremental development with GHCiEdit

Debugging with HatEdit

General tipsEdit

Last modified on 12 June 2012, at 03:06