Haskell Debugger

I too mostly use trace (traceShowId and other forms), usually inserted very temporarily at each place of interest, reloading and executing in GHCI for speed. I also use these helpers. The main things here are

  • pretty printing the traced values for readability (using pretty-show; shower is a new contender)
  • dbg* which are trace statements you can leave in your code permanently, which fire only when a program-wide debug level is above a required value (can be set eg by a --debug command line option).
2 Likes

For pure functions, debug is godsent as it gives you some HTML like this to play with:

(much much cleaner than a series of :step or compulsive traceing).

You can even get a plain-text version of the above with debugPrint and never have to leave GHCI, if you prefer.

3 Likes

I almost never need to debug pure functions and data structures, if I do I make use of the functions in “Debug.Trace” module. Most of my problems in Haskell occur near the level of the IO monad, usually due to my misunderstanding of how software external to my program is behaving. For cases like this, I just print out to console log and observe the order of events as they occur in the program.

I have tried using GHCi as a debugger, which provides you with source-level annotations of where function evaluation is occurring at any given time, and allows you to observe values of bound variables that have been non-lazily evaluated, but this is usually much, much more difficult and time consuming as compared to using one of the “Deubg.Trace” functions and just looking at the order of program execution events in the log.

I should also add, I use Haskell professionally, and all of my team does it the same way as me: no interactive debugging at all, we run tests, if something doesn’t work, we just increase the number of log messages in the console.

Whilst all the previous comments are true, I find it a bit disappointing how bad the debugging story is in GHC. One of the reasons why the debugger is seen as useless is that no one has taken ownership of that part of the compiler for many years. Someone who was dedicated to making it useful could make some rapid improvements in this area I think.

Haskell also lags behind other languages when it comes to memory/time profiling, if someone were to take a dedicated interest in this as well then debugging these issues would become far easier.

17 Likes

Adding my voice to the chorus: I don’t think I’ve ever used a debugger on my Haskell code. The combination of pure functions and immutable data makes it much less necessary. I do sometimes turn to trace and print, but more often than not unit tests are the way I “debug” things.

I’ll echo @mpickering here and say that the debugging and profiling tools leave quite a bit to be desired when trying to track down tricky edge cases in Haskell code.

I think Tikhon Jelvis’ overview of the tools we have at our disposal is quite good if you’re looking to get an idea of what the best practices are overall, though.

1 Like

Unpopular opinion, but a debugger is an extremely valuable tool for much more than debugging. It’s how I learned how various instance resolution pragmas worked eg. MultiParamTypeClasses, IncoherentInstances and OverlappingInstances. As awful as it is to use without it I would never have made any sense out of continuations (especially delimited) and various other weird control flow constructs. Even in pure code it’s invaluable if your function is going down a pattern match arm you didn’t think of since order of patterns matters. You can also use it to visualize/understand laziness, “time travel” and all kinds of fun things. I get the feel that the whole I-don’t-need-one-because-purity is coping mechanism because it is quite a pain to use.

15 Likes

Hi @deech

Here is one positive short story of using a debugger on another lang.

It’s related to “data-mining”-like work, a difficult (at the time & for me) problem with no clear solutions on the literature. Thus, I throw some heuristics around, some based on the standard ways of doing things and some other based on intuitive ways to tackle the problem (greedy ways to solve the problem).

Then, after some frustration on why my brilliant greedy algs didn’t work, learned to use a debugger, found out that the algs were doing and working as initially thought out, and at the same time found out interesting patterns emerging from the solution space. By just using printf’s, this wouldn’t have happened.

This eventually lead to such master theses that my supervisor suggested me to do a phd.

So, as @mpickering and @jkachmar
tell above, a good debugger can help to gain understanding on problems, better working solutions faster on problems areas that are possibly ill-defined, vague or other way not so well known. I think that this is quite common on data mining, optimization and ai related work.

Ok, I have tried to initiate conversation on “road-maps” and such things and I think that improving the tooling is one thing that should be heavily present in there.

1 Like

In both work and open source projects I only use tests and trace debugging. Not because that’s what I think is optimal, but because it’s what works for me currently. I often miss an ergonomic step debugger, especially something integrated with my editor. There’s some basic support in Emacs for using the GHCi debugger, and I confess to not having tried it out properly, but my experiences with GHCi debugging have not been great so far. I should give it a fair try, though. Very much agree with @mpickering’s and @deech’s comments.

3 Likes

Ah, I forgot I wrote that :P. I keep on meaning to extract Quora posts to a non-Quora blog, and this is probably a good one to start with.

I really do wish we had something like console.log for Haskell, but developing tools like that is hard. I wonder if there’s something GHC could to do make interactive tooling like debuggers and IHaskell easier to develop and maintain?

I just use hp2pretty with GHC and threadscope (also with GHC).

If you use Megaparsec, definitely check out dbg from Text.Megaparsec.Debug: http://hackage.haskell.org/package/megaparsec-7.0.4/docs/Text-Megaparsec-Debug.html#v:dbg

This is a pretty dumb one I came up just for Parsec. https://www.reddit.com/r/haskelltil/comments/3el2d6/a_handy_function_for_debugging_in_parsec_by_what/

Something similar made its way into parsec proper with parserTrace: http://hackage.haskell.org/package/parsec-3.1.13.0/docs/Text-Parsec.html#v:parserTrace

That looks useful! I’ll have to try that out sometime.

While on this topic, has anyone done debugging of GHC-compiled executables using LLDB and VSCode? I’ve got the basic LLDB thing working on the command line, but not integrated with VSCode. Would be nice to have that “set breakpoint by clicking next to the line number” experience.

I’ve only tried using LLDB on the simple fib example from the wiki, not sure if it’s even feasible to debug larger programs like this and get any decent introspection.

I have not tried that yet. Is there a specific LLBD extension for vscode? or are you using the built-in debugger interface? Why LLDB rather than GDB?

Yeah, there is. I wasn’t aware it had GDB support. I’ve tried that now and got it to work, but the experience is (as without VSCode) not great. Had local expressions been visible it would be quite useful, I assume. Or is that supposed to work?

I’m not sure what is and isn’t supposed to work. This isn’t well documented at all.

You might also be interested in phoityne which integrates GHCi’s debugger with VSCode. I haven’t tried it yet.

2 Likes

Yeah, it’s on my radar. Haven’t found the time to try it yet. Thanks!