FWIW, it seems to be a common experience that if you come from an object-oriented background, you'll find that you need a debugger less with functional programming. I don't know if that's your background, but that's been my journey. In the few couple of years I've been writing Haskell code, I've never looked into how to debug it.
I sometimes have to debug F# code, but only when it interacts with the object-oriented parts of .NET.
Debuggers let you inspect the internal state of variables at various stages through a computation. That makes sense when code involves mutable state, but becomes less important when everything is immutable, and when expressions are referentially transparent.
What I normally do when I don't understand a piece of Haskell code is that I start decomposing it and play around with the various sub-expressions in GHCi. In this particular example, you could do something like the following.
First of all, it's hopefully clear what happens when the input is []
or [x]
:
Prelude> bubblesort2 []
[]
Prelude> bubblesort2 [42]
[42]
I'll assume that the part of the code you want to understand is the bubblesort2 (x:y:rest)
case. What I'd do, then, is move on from []
and [42]
to the next-simplest case, where you have exactly two values:
Prelude> bubblesort2 [1337,42]
[42,1337]
This corresponds to the bubblesort2 (x:y:rest)
case, where:
Prelude> x = 1337
Prelude> y = 42
Prelude> rest = []
Notice that I just bound values to the symbols x
, y
and rest
in GHCi. This enables you to evaluate the first expression in the where
block in the function:
Prelude> (first,second) = if x > y then (y,x) else (x,y)
Prelude> first
42
Prelude> second
1337
The next thing you can do is to run the bubblesort2(second:rest)
sub-expression:
Prelude> bubblesort2(second:rest)
[1337]
If you need a reminder on why this is the outcome, you can even inspect second
, rest
, and second:rest
:
Prelude> second
1337
Prelude> rest
[]
Prelude> second:rest
[1337]
Sooner or later, you may come to the realisation that this is the bubblesort2 [x]
case, and that's why bubblesort2(second:rest)
returns [1337]
. You should now have a good idea what bubbled
is, but otherwise, you can evaluate that as well:
Prelude> bubbled = first : bubblesort2(second:rest)
Prelude> bubbled
[42,1337]
Moving on, you can now start to decompose the main body of bubblesort2
. First, for instance:
Prelude> [last bubbled]
[1337]
and:
Prelude> init bubbled
[42]
So, again, bubblesort2 (init bubbled)
matches the bubblesort2 [x]
case, so that you get:
Prelude> bubblesort2 (init bubbled)
[42]
and finally:
Prelude> bubblesort2 (init bubbled) ++ [last bubbled]
[42,1337]
Going through steps like these should enable you to understand the case where the list has exactly two elements. Once that works for you, you can move on to re-bind the values in GHCi to e.g. walk through the case [1337, 42, 12345]
:
Prelude> x = 1337
Prelude> y = 42
Prelude> rest = [12345]
Prelude> (x:y:rest)
[1337,42,12345]
I'm not going to walk you through this case, but I hope it's clear how you could work it through in the same way as the above.
I think I know what you'll say:
Do I have to do this every time I have to debug?!
It's my experience that when you start with Haskell, or another functional programming language, there's a lot that you don't understand, and you'll feel the need to reach for a debugger often.
That's just a phase, and it'll pass.
Playing around with code in a REPL is a more idiomatic approach to functional programming, and once you get used to it, you'll tend to always have a REPL open. This is true for me both when it comes to Haskell and F#, and I've heard other functional programmers say the same. It seems to be the case for Clojure programmers as well.
To be clear, these days, I rarely feel the need to step through Haskell code at the level of detail outlined above. Usually, there's just a single expression or two that I find difficult to understand, and then I just isolate that and play with it in GHCi until I understand what's going on.
Working things out in GHCi will, I think, give you a better long-term understanding of Haskell than trying to get a debugger working.