Imperative vs Functional – Differences in Language Debuggers

debuggingfunctional programmingimperative-programming

Up to now I have always worked with imperative languages like Pascal, C, C++, Java in a production environment, so I have experience with debuggers for these languages (Turbo Pascal, Turbo C, GDB / DDD, Visual Studio, Eclipse).

A debugger for an imperative language normally allows to

  • Set break points on statements, and execute the program until the next break point is encountered.
  • Execute the program one statement at a time, with the option of entering a function / method call or skipping over to the following statement.

When program execution is paused, one can examine the current state of the program, i.e.:

  • Inspect the contents of the stack to see the current nesting of function calls.
  • Inspect local variables, actual function parameters, global variables, and so on.

Since I have never worked with a functional programming language in a production environment, I was trying to figure out how a debugger for such a language would look like. For example:

  • How does the debugger walk through the program if there are no statements like in an imperative language? Where does one set break points?
  • In a language with lazy evaluation like Haskell, the sequence of function calls can be different from that of an eager language: will this make a big difference when trying to understand the call stack?
  • What about variables? Since there is no state, I imagine that a debugger will only show the variable bindings for the current scope (?), i.e. there will be no local or global variables changing value as I step through the program.

Summarizing, are there any general, common features of functional-language debuggers that clearly distinguish them from imperative-language debuggers?

Best Answer

Deep under the hood, any functional language with heavy roots in lambda calculus is compiled to code, closely following some abstract computation model. For haskell it is STG-machine, for ocaml/caml-light it was CAM-machine. This execution model usually has something like local variables and clear route of execution and source code translates into code for abstract machine by clear, well-defined algorithm. So, breakpoints can be inserted in haskell (for example) code and something like local variables can be determined, when expression with breakpoint is evaluated.

For haskell and other lazy languages, however, the problem is that it does not make much good (except profiling). REPL allows coding expressions with easy, as no side-effects goes away from function body definition, so no real need to debug functions locally and asserts will locate error for sure. The real source of bugs usually is space leak due laziness in wrong place, and completely different tools, inspecting heap structure, are needed (and some are available).

Related Topic