The use of a debugger usually indicates that a programmer lost a control of its own program and has no idea whats going on.
If a programmer doesn't own a program, and tries to understand existing program, written by someone else, then it means to me,
that the program is written so poorly, that it is too hard to recover its semantics by reading its source code representation.
OCaml, as well as any other functional programming language, encourages to compose big programs from small and understandable
pieces. Due to unique properties of functional programming paradigm this composition is usually linear (lack of mutable state and side-effects). Also,
OCaml has a reach type system, that allows a programmer to specify his assumptions and program invariants, so that any violation
will be caught on compile time.
So, if properly applied, OCaml allows one to write big programs without need for debugging at all. Of course, it is possible to write Fortran 77 in
OCaml, in that case you will soon find yourself in the debugger prompt.
Sometimes, though, especially when you heavily interact with outside world, like user interface, networking, etc, you find yourself on a loose ground,
since your assumptions cannot be defined in terms of OCaml typesystem (no dependent typing, no linear typing). In that case you need to study
the dynamics of a program. Usually, in this cases the debugger is not useful, since the program interacts in real-time with some external agent, that is
not going to wait for you.
My personal approach to debugging is to start from static analysis of the code and try to limit the search space. I also try to extract all possible assumptions
that was made by a programmer. Once I have a list of possible suspects, I instrument the code and insert some tests. Sometimes the instrumentation is just
a printf, sometime it is just an assert. Quite often, this is just `assert false`. I continue this operation, until only one suspect is left. In most use cases, the
initial stage of reading code, will already leave me with only one or two suspects, as if the code is written properly it is usually easy to find a proof (an alibi) for
most cases. Of course this require some programming discipline:
1. limit the use of mutual state
2. limit the use of exceptions in favor of returning variant types (option, result, or_error)
This two rules ensures linearity, due to the lack of side effects. And usually, this is side effects, that we're trying to debug with a debugger or printfing.