Haskell/Debugging
Debug prints with Debug.Trace
[edit | edit source]Debug prints are a common way to debug programs. In imperative languages, we can just sprinkle the code with print statements to standard output or to some as log file in order to track debug information (e.g. value of a particular variable, or some human-readable message). In Haskell, however, we cannot output any information other than through the IO monad; and we don't want to introduce that just for debugging.
To deal with this problem, the standard library provides the Debug.Trace. That module exports a function 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
when 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 it 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 advice
[edit | edit source]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. Yet surely trace
is doing IO while printing useful messages. What's going on? In fact, trace
uses a dirty trick of sorts to circumvent the separation between IO and pure Haskell. 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.
A common mistake in using trace
: while trying to fit the debug traces into an existing function, one accidentally includes the value being evaluated in the message to be printed by trace
; e.g. don't do anything like this:
let foo = trace ("foo = " ++ show foo) $ bar
in baz
That leads to infinite recursion because 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 correct trace message should have show bar
:
let foo = trace ("foo = " ++ show bar) $ bar
in baz
Useful idioms
[edit | edit source]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 GHCi
[edit | edit source]Debugging with Hat
[edit | edit source]General tips
[edit | edit source]This page is a stub. You can help Haskell by expanding it. |