22

I am new to Haskell. Previously I have programmed in Python and Java. When I am debugging some code I have a habit of littering it with print statements in the middle of code. However doing so in Haskell will change semantics, and I will have to change my function signatures to those with IO stuff. How do Haskellers deal with this? I might be missing something obvious. Please enlighten.

Jackson Cotwannaie
  • 221
  • 1
  • 2
  • 3
  • Here's how to use `trace` in a do statement: `trace (show state) (return ())` or `trace "hello!" (return ())` – Richard Sep 30 '22 at 16:24

6 Answers6

10

Other answers link the official doco and the Haskell wiki but if you've made it to this answer let's assume you bounced off those for whatever reason. The wikibook also has an example using Fibonacci which I found more accessible. This is a deliberately basic example which might hopefully help.

Let's say we start with this very simple function, which for important business reasons, adds "bob" to a string, then reverses it.

bobreverse x = reverse ("bob" ++ x)

Output in GHCI:

> bobreverse "jill"
"llijbob"

We don't see how this could possibly be going wrong, but something near it is, so we add debug.

import Debug.Trace

bobreverse x = trace ("DEBUG: bobreverse" ++ show x) (reverse ("bob" ++ x))

Output:

> bobreverse "jill"
"DEBUG: bobreverse "jill"
llijbob"

We are using show just to ensure x is converted to a string correctly before output. We also added some parenthesis to make sure the arguments were grouped correctly.

In summary, the trace function is a decorator which prints the first argument and returns the second. It looks like a pure function, so you don't need to bring IO or other signatures into the functions to use it. It does this by cheating, which is explained further in the linked documentation above, if you are curious.

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Adam Burke
  • 724
  • 1
  • 7
  • 21
  • 3
    Very important to note: use `trace` with the compiler in mind. For example, wrapping an unused value by `trace`, or a value assigned to a variable in a `where` block for which the compiler can simply substitute the variable in the output expression, or assigning trace to an unused value ... will all result in `trace` being ignored. *From the docs: You must keep in mind that due to lazy evaluation your traces will only print if the value they wrap is ever demanded.* – Mew Nov 23 '21 at 00:06
  • How do you do this inside a `do` block? I've tried `let x = trace "hello"` and `x <- trace "hello` and just `trace "hello"`, but they are all syntax errors – Richard Sep 30 '22 at 16:17
  • Note that the signature of trace is `trace :: String -> a -> a`. So it requires two parameters, whether in or out of a `do` block. So a line like `do ( trace "Hello" 1 )` will work, where the 1 is a dummy parameter, given you aren't using the return value. That said, if you are in a do block because of needing a side-effect like accessing IO, you can just use `putStr` directly, as in, `do ( putStr "Hello" )`. – Adam Burke Oct 01 '22 at 09:45
  • None of the links explain the cheat you are talking about. I've been searching everywhere trying to find out how can trace possibly exist in Haskell. I don't understand – Stefan Octavian Mar 30 '23 at 10:38
  • It's true that the level of detail of the links is that they use "a dirty trick of sorts", or, as in the `trace` function, says "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. " The exact how isn't there. The GHC source is linked, though, and there you can see that `trace` calls [`unsafePerformIO`](https://hackage.haskell.org/package/base-4.18.0.0/docs/System-IO-Unsafe.html#v:unsafePerformIO), which has a type signature of `IO a -> a ` and is described as "the back door to the IO monad". – Adam Burke Mar 30 '23 at 11:41
  • Asking how `unsafePerformIO` works under the hood might be an interesting question in its own right, or on a quick skim of SO, some of the answers to the existing questions on that topic might fit the bill. – Adam Burke Mar 30 '23 at 11:55
  • Eg [How does ghc implement `unsafePerformIO`?](https://stackoverflow.com/questions/47929099/how-does-ghc-implement-unsafeperformio) – Adam Burke Mar 30 '23 at 11:58
5

Read this. You can use Debug.Trace.trace in place of print statements.

sinelaw
  • 16,205
  • 3
  • 49
  • 80
  • 4
    It's utterly baffling how you would ever use `trace`. I can't get it to work and that documentation is really really bad. – Alper Dec 09 '19 at 22:22
4

I was able to create a dual personality IO / ST monad typeclass, which will print debug statements when a monadic computation is typed as IO, them when it's typed as ST. Demonstration and code here: Haskell -- dual personality IO / ST monad? .

Of course Debug.Trace is more of a swiss army knife, especially when wrapped with a useful special case,

trace2 :: Show a => [Char] -> a -> a
trace2 name x = trace (name ++ ": " ++ show x) x

which can be used like (trace2 "first arg" 3) + 4

edit

You can make this even fancier if you want source locations

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Syntax as TH
import Debug.Trace

withLocation :: Q Exp -> Q Exp
withLocation f = do
    let error = locationString =<< location
    appE f error
    where
        locationString :: Loc -> Q Exp
        locationString loc = do
            litE $ stringL $ formatLoc loc

formatLoc :: Loc -> String
formatLoc loc = let file = loc_filename loc
                    (line, col) = loc_start loc
                in concat [file, ":", show line, ":", show col]

trace3' (loc :: String) msg x =
    trace2 ('[' : loc ++ "] " ++ msg) x
trace3 = withLocation [| trace3' |]

then, in a separate file [from the definition above], you can write

{-# LANGUAGE TemplateHaskell #-}
tr3 x = $trace3 "hello" x

and test it out

> tr3 4
[MyFile.hs:2:9] hello: 4
Community
  • 1
  • 1
gatoatigrado
  • 16,580
  • 18
  • 81
  • 143
  • 1
    I don't think this answer is suitable for a beginner. – Alper Dec 09 '19 at 22:23
  • @gatoatigrado can you please elaborate your answer? Suppose I have a recursive function (fun :: a -> a) with multiple pattern-matching implementations of it. I want to know the order of execution, by line numbers. I write this (fun arg | $trace3 ("fun : " ++ show arg) False = undefined) after the type declaration, which prints me line numbers, but they all link to this particular line, not the implementations. In short, I want simple-one-line-enable-trace-debug. – nutella_eater Aug 01 '22 at 19:32
2

You can use Debug.Trace for that.

bzn
  • 2,362
  • 1
  • 17
  • 20
0

I really liked Dons short blog about it: https://donsbot.wordpress.com/2007/11/14/no-more-exceptions-debugging-haskell-code-with-ghci/

In short: use ghci, example with a program with code called HsColour.hs

 $ ghci HsColour.hs
    *Main> :set -fbreak-on-exception
    *Main> :set args "source.hs"

Now run your program with tracing on, and GHCi will stop your program at the call to error:

 *Main> :trace main
    Stopped at (exception thrown)

Ok, good. We had an exception… Let’s just back up a bit and see where we are. Watch now as we travel backwards in time through our program, using the (bizarre, I know) “:back” command:

  [(exception thrown)] *Main> :back
    Logged breakpoint at Language/Haskell/HsColour/Classify.hs:(19,0)-(31,46)
    _result :: [String]

This tells us that immediately before hitting error, we were in the file Language/Haskell/HsColour/Classify.hs, at line 19. We’re in pretty good shape now. Let’s see where exactly:

 [-1: Language/Haskell/HsColour/Classify.hs:(19,0)-(31,46)] *Main> :list
    18  chunk :: String -> [String]
        vv
    19  chunk []    = head []
    20  chunk ('\r':s) = chunk s -- get rid of DOS newline stuff
    21  chunk ('\n':s) = "\n": chunk s
                                       ^^
Jonke
  • 6,525
  • 2
  • 25
  • 40
  • This answer unfortunately is a nice example of why link-only answers can be a problem - the page they used to reference to may have changed (in this case: it appears to be gone). – Frerich Raabe Dec 14 '17 at 10:19
-2

This might be an unorthodox answer to this question, but, let me be plain:

  • Q: How to do typical "print"-style debugging of code in Haskell?
  • A: Do not do it. Do not do "print"-style debugging in Haskell. Do not do it in any programming language! Learn how not to do it!

I have a habit of littering it with print statements in the middle of code.

This is a very bad habit. A lot of programming students pick up this habit when they are learning how to do mental code walkthroughs (of course I have picked up this habit myself). This habit stays with the students, and it is hard to get it out. It is a waste of time to put and then subsequently remove print statements throughout your code in order to ensure the correctness of your code and in order for you to understand what is going on.

You should not litter your code with print statements. In any programming language. Instead of littering the code with print statements, you should write tests. In the case of Haskell, you should learn to write doctest, or unit tests with hspec Use the tests to help you to understand what works and what does not, and write tests to ensure the correctness of your functions.

And for understanding Haskell code, use GHCI. Play with the functions with the interactive interpreter. Load the module you are working on, and interactively "inspect it" with GHCI.

Of course, there are situations where debugging is required. All the other excellent answers will give you some insights on how to go about it in Haskell. But, debugging in Haskell is a harsh mistress, and in my personal experience it is better to be avoided. Unless you really, really need to do it.

marni
  • 4,214
  • 1
  • 24
  • 18