Tuesday, July 14, 2009

Haskell kenken debugging

Programming in Haskell requires a different kind of debugging process. Some debugging can be eased by using something like quickcheck and Hat provides tracing facilities. I've used both of these and quite like quickcheck as it provides a nice way to check low level code. I usually find though that I'd like to do some (often selective) printing of specific information in a format that I can easily scan and interpret. I often spend a fair amount of time building output that is easy to look at as doing that both helps me debug and helps me to ensure that I understand what it is I'm trying to do.

In a language like C you can just dump in printfs here and there (usually controlled by some debug value either defined at the preprocessor level or in the language). But in Haskell it is much harder to just dump IO statements in at random points in the code unless that code is in the IO monad.

In this code, I've used the state transformer monad on top of the IO monad for the highest level of code, so the IO monad is available. To get the same effect as in C where I can turn on and off such statements by changing the value of a variable, I've built some low level helper functions. The essential part of the code is :

debug = False

dbPutLn :: String -> PuzzleM ()
dbPutLn s = if debug then liftIO $ putStrLn s else return ()


Because of the liftIO this needs to be run in the appropriate monadic environment, but thats not a problem here.

Along with that I've also constructed a way to print multiple lines with a label and so that the label lies up against the left hand side of the output and the lines all are indented a bit. This makes the output easier (for me at least) to scan for interesting occurrences. Typical output might look like :

in function foo
value1=1
value2=2

And the code for this looks like :

dbPutLabeledLines :: String -> [String] -> PuzzleM ()
dbPutLabeledLines label ls = if debug
then do
liftIO $ putStrLn ""
liftIO $ putStrLn label
mapM_ (liftIO . putIndentedLine) ls
else return ()
where
putIndentedLine l = putStrLn $ " " ++ l


On a side note, languages that provide support for pre-conditions, post-conditions and class invariants (such as Eiffel and the sadly defunct Sather) are, for me anyway, about the best thing around for building correct code. Not only can good pre/post-conditions make debugging easier (especially with a good test suite and a good way to generate test cases), but they also help me decide just what it is that the code needs to do and they provide invaluable information when I read the code to see what it does.

2 comments:

Don Stewart said...

Have you looked at 'trace' or 'assert' in the base library, for debugging ?

jefu said...

I have looked at trace and assert and have used both - they are helpful, but I really, really like the precondition/postcondition methods as they require me to think far more deeply about what I'm actually trying to accomplish.