2
bubblesort2 :: (Ord a, Show a) => [a] -> [a]
bubblesort2 [] = []
bubblesort2 [x] = [x]
bubblesort2 (x:y:rest) =
    bubblesort2 (init bubbled) ++ [last bubbled]
    where
        (first,second) = if x > y then (y,x) else (x,y)
        bubbled = first : bubblesort2(second:rest)

I'm trying to understand the above haskell code. I tried to debug the code in intellij,jetbrains haskell plugin, but for some reason it throws debug execution error. Is there any nice way to debug through ide. The normal debugging through gchi seems to be too complex.

Curious
  • 921
  • 1
  • 9
  • 25
  • You have posted type-correct code, said you want to understand it and debugging is hard. What you are looking for in a response? I've voted to close since this seems too broad and lacks and structure or specificity. – Thomas M. DuBuisson Jan 11 '18 at 16:17
  • 1
    Is there any good debugger(ide based) which is easy to debug just like for scala, java, c++. – Curious Jan 11 '18 at 16:19

1 Answers1

14

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736