One of the things that I really dislike about Mathematica is its lack of debuggability compared to many other programming languages. Some of the problems arises from the functional nature of Mathematica. However, some of them arises from the current state of Mathematica's development environment and might be improved in later versions. Here is an incomplete list of reasons why Mathematica is less-debuggable compared to other languages. I guess some of you may disagree with some of the reasons below, but most of you agree that such a problem exists.
- In Mathematica, function chaining is a common practice (by function chaining I mean calls like
f[g[h[x]]]). That is, fewer variables are introduced to the programs. As a result, there are fewer intermediate points to check for the expected values. It makes it difficult to narrow down the scope while searching for a problem.
- In Mathematica iterative constructs such as
Whileare usually replaced by block constructs such as
Array. Therefore, while in imperative languages one can narrow down the focus to a single iteration of a loop (and even narrower), in Mathematica, it's not an easy job.
Mathematica doesn't Fail-fast. For instance, if you have a call on an undefined function
f, it will continue computing. Note that, it may be the desired behaviour in some cases but generally it reduces debuggability. The problem becomes more severe when you pass such a function to another function or a function chain. Another (maybe more complicated) instanc follows:
Testis supposed to be called on Integers only, but it doesn't fail if it is called on other types. In some cases we can prevent such an issue by replacing the above line with:
Test[_] := Throw["Incorrect function call"];
Anonymous functions and lazy evaluation are commonly used in Mathematica.
Unlike many other languages such as Java, Mathematica doesn't produce a full stack trace while encounters an exception. It makes it difficult to trace the problem when an exception is thrown (there might be an option to turn on the stack traces that I am unaware of).
Mathematica keeps the state of notebook between different runs. One can define a function or variable and evaluate the cell. Then delete the cell and the function or variable is still defined and can be used.
Variables and functions are defined in the global context by default unless otherwise stated (e.g., using
Withfunctions), while in many other languages variables are defined locally by default. Different notebooks share the same context by default that adds to the complexity of debugging. This is against the principle of least privilege.
Now, the question is: what are the good practices to prevent errors in Mathematica and what are the best debugging techniques if you encountered a problem. I think that's a good idea to have one solution per answer so we can discuss about it easily.