How to debug the OCaml code more easilly?

Hi all, is there some easier way to debug the OCaml code? I am reading an OCaml project recently, i have tried serval ways to understand the code logic, include using the debugger ocamldebug to debug the code, using print_endline to log the variable in console. This two way are all inefficient, the ocamldebug is too difficult to use relative to the other language’s debugger(java, js, go, python…even rust), another one, OCaml does not support a polymorphic print function, this make it so hard to quickly print a complicated variable to the console when the type of variable has nested variant field especially.
Could you give me some tips to debug the OCaml code? I am a little stuck for now.

1 Like

You could try ppx_minidebug: trace selected code if it has type annotations. Unfortunately it’s not in the opam repository yet. I’m sure others will have more suggestions.

1 Like

[this doesn’t work for highly-concurrent code, but …]
I use the toplevel. I load a bunch of packages, and then reproduce whatever behaviour I want to debug, in the toplevel. I often use a file like this ( ) that I can #use, adding stuff to the end to allow a repeatable reproduction. Then once I have reproduction, I add #install_printer to print out various types, and #trace to trace various functions. Bit-by-bit, I can home in on where the bug is happening, and why.

For concurrent code, in OCaml and esp. other languages (e.g. Java) I’ve used hand-written or mechanically-introduced logging statements; then run the system under load to elicit the bug, then process the logs to extract useful information, probably adding more logging, then more runs, until I can get the information I need.

To be frank, once I started working with concurrent systems (Java spit) I stopped using debuggers: you can’t use a debugger on a concurrent system (I used to say “a debugger makes too much noise, scares the beast and it runs off into the woods”) without perturbing it sufficiently to make the bug disappear, after all.

ETA: I think it’s really important to ensure that at every step of the debugging process, you can kill the OCaml process (the “system under test”) and rapidly resurrect it to the same spot you were at. That is to say, you need to preserve the reproducibility of the bug, while you insert instrumentation. This is why I use a file containing all the toplevel commands needed to reproduce the bug while also inserting all instrumentation needed to understand it.
ETA2: automated post-processing of logs is your friend. Even when using #trace, I often take the (Emacs) buffer of the toplevel interaction and postprocess to remove some log-lines, maybe rewrite others in a systematic way, etc. When I’m using logging statements, I take the logfiles and postprocess them (typically with one-off Perl scripts) to help analyze what happened.

1 Like