Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "debug" Haskell with printfs?

coming from the Ocaml community, I'm trying to learn a bit of Haskell. The transition goes quite well but I'm a bit confused with debugging. I used to put (lots of) "printf" in my ocaml code, to inspect some intermediate values, or as flag to see where the computation exactly failed.

Since printf is an IO action, do I have to lift all my haskell code inside the IO monad to be able to this kind of debugging ? Or is there a better way to do this (I really don't want to do it by hand if it can be avoided)

I also find the trace function : http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends which seems exactly what I want, but I don't understand it's type: there is no IO anywhere! Can someone explain me the behaviour of the trace function ?

like image 478
Vinz Avatar asked Aug 23 '10 10:08

Vinz


People also ask

How do I debug a Haskell program?

Debugging. To debug the main function of a file, press cmd-shift-p (Mac) or ctrl-shift-p (Linux/Windows) to launch the command palette, type in haskell debug and press enter.

Is there a Haskell debugger?

Hoed - The Lightweight Haskell Tracer and Debugger Hoed is a tracer/debugger that offers most of HATs functionality, and works with untransformed libraries. Hoed can therefore be used to debug much more programs than traditional tracer/debuggers.

What does debug trace do?

Debug. Trace. Functions for tracing and monitoring execution. These can be useful for investigating bugs or performance problems.


2 Answers

trace is the easiest to use method for debugging. It's not in IO exactly for the reason you pointed: no need to lift your code in the IO monad. It's implemented like this

trace :: String -> a -> a trace string expr = unsafePerformIO $ do     putTraceMsg string     return expr 

So there is IO behind the scenes but unsafePerformIO is used to escape out of it. That's a function which potentially breaks referential transparency which you can guess looking at its type IO a -> a and also its name.

like image 170
Daniel Avatar answered Sep 24 '22 18:09

Daniel


trace is simply made impure. The point of the IO monad is to preserve purity (no IO unnoticed by the type system) and define the order of execution of statements, which would otherwise be practically undefined through lazy evaluation.

On own risk however, you can nevertheless hack together some IO a -> a, i.e. perform impure IO. This is a hack and of course "suffers" from lazy evaluation, but that's what trace simply does for the sake of debugging.

Nevertheless though, you should probably go other ways for debugging:

  1. Reducing the need for debugging intermediate values

    • Write small, reusable, clear, generic functions whose correctness is obvious.
    • Combine the correct pieces to greater correct pieces.
    • Write tests or try out pieces interactively.
  2. Use breakpoints etc. (compiler-based debugging)

  3. Use generic monads. If your code is monadic nevertheless, write it independent of a concrete monad. Use type M a = ... instead of plain IO .... You can afterwards easily combine monads through transformers and put a debugging monad on top of it. Even if the need for monads is gone, you could just insert Identity a for pure values.

like image 25
Dario Avatar answered Sep 22 '22 18:09

Dario