Comment by tmhedberg

14 years ago

In addition to the Debug.Trace facility that others have mentioned, it's also worth noting that GHCi has a built in debugger. I find that I need it pretty infrequently, since you can inspect anything defined at the top level simply by loading your module in GHCi and evaluating the expression in question. But if you need to inspect more local bindings, such as functions defined in a `where` clause in the body of a function, the debugger works really well.

The only funny thing about it is that, since Haskell's syntax isn't organized in the "one statement per line" manner like most imperative languages, you often have to specify breakpoints not just by the line where execution should pause but by the column as well. For this, I recommend using an editor that can easily display the column number that the cursor is in.

Most of the author's other comments I can understand, even if I don't share his opinions. There is certainly a cognitive barrier to entry inherent in a lot of the language's concepts. All I can say about this is that as I've used the language more and more, these barriers seem to have melted away to the point where, simply by browsing the types of names exported by a library module, I can pretty much intuit how it works. A lot of libraries are very well-documented, and some are definitely not, but even in the latter case, the type signatures alone can go a long way to helping me understand.

I'd probably be a Scheme addict myself if Haskell hadn't convinced me of the indispensable power of strong static types. I love how elegantly simple Scheme is, and how you can construct so much from such humble beginnings. It feels like it distills functional programming down to its very essence. SICP is a beautiful thing as well. But Haskell naturally guides me to writing better, more robust, more correct code in ways that dynamic languages simply aren't capable of, and I just find myself getting frustrated when using other languages because I always end up wasting time fixing bugs that I know GHC would have caught for me up front.

Someone in the Haskell community (I can't recall who it was) has said: "I think of types as warping our gravity, so that the direction we need to travel to write correct programs becomes 'downhill'." I couldn't agree more.

Edit: I also have to state my disagreement about monads being a "wobbly crutch". While it is true that they are more or less necessary in order to introduce practical impurity into Haskell, being necessary doesn't make them a shim or a hack. Not only are they an elegant solution to many problems, but they are really quite simple to use. If people spent as much time actually writing monadic code as they do reading the zillions of silly, confusing monad tutorials, they'd quickly reach the point where they no longer see what the big fuss is about. There is nothing truly scary here; the worst part about monads is the name.

Assuming that Debug.Trace offers similar functionality as "observe" does, I have to briefly mention that this tracing might not be overly helpful, precisely because of the lacking control over evaluation order, as you write later on. I had to write a parser using both, combinatoric and monadic style, and debugging was a major turn off because I could not easily see what went wrong (though I seem to remember that using observe the ouptut was reverse to actual program flow, at least the one I have in my head.)

  • I usually use `trace` like so:

        f _ _ _ | trace "print something profound" False = undefined
        f normal arguments here = ...
    

    That way the trace output will be printed whenever the function is evaluated, which typically mimics a lexical call stack unless you use laziness in some particularly interesting way.