Debugging tips
Although strictly-typed functional languages like OCaml greatly reduce the number of bugs in your programs, you will still make mistakes and have to debug them. Debugging functional languages can be challenging; here are some tips we've found to be useful.
printf
-style debugging
printf
-style debugging is highly effective if you do it right. The wrong
way is to add printf
statements to otherwise functional code:
Even though this can work, you are embedding imperative code inside functional code, and you can get into tricky syntax issues as a result. A better way to do it is like this:
This also embeds imperative code, but uses functional syntax, so there won't be any syntax issues.
Note
If the surrounding code is imperative (like a begin
expression),
the exact opposite criterion applies:
favor the naked Printf.printf ...
instead of the let _ = Printf.printf ...
idiom.
Also, you should add %!
at the end of the printf
format string:
This makes OCaml flush the string as soon as the printf
executes.
If you don't do that, sometimes error messages before a crash can be lost.
Finally, for debugging, consider writing your printf
s unindented this way:
This way, it's easy to spot these lines, which is good because you are going to want to delete them once debugging is done.
Using the REPL
"REPL" means "Read-Eval-Print-Loop" and refers to the interactive interpreter
(what you get into if you type ocaml
or utop
at a terminal prompt).
You might think that it's hard to use a REPL in a large, multi-file project
like the compiler assignments, but we've got you covered!
Typing make repl
in any directory will bring up a REPL in which all the
modules of the program are accessible. This allows you to interactively test
your code. Some kinds of code are easier to test in this way than others, but
don't underestimate how useful this can be, especially when trying to track
down a tricky bug. All you need to do is to open the relevant module:
and you can play with all the functions in that module.
Insert debugging code into tricky algorithms
Some passes involve tricky algorithms. In those cases, it's a good idea to
define a debug
variable at the top of the file:
and then put debug code into the tricky algorithm that prints out the inputs and outputs:
let tricky_algorithm weird_inputs =
if !debug then
Printf.printf "INPUT: %s\n" (string_of_weird_inputs weird_inputs);
... (* rest of algorithm *)
To do this, you need to be able to convert the algorithm inputs/output
to strings. Most of the data structures you will be using can be easily
converted to S-expressions, and there is a print_sexp
function in the
Utils
module that will print an S-expression in a readable fashion
(this is discussed in the next section).
This is extremely useful for tracking down algorithm errors.