One of the great things about Mathematica is that it makes it so easy to do so much in so little code. It is partly because of this that I am so frustrated with the amount of time I need to spend developing the interface of package functions compared to their functionality.
Suppose you wrote a Wolfram Language function that does something useful and interesting. Now you want to wrap it up into a package, and make it usable by everyone. A polished package is expected to have functions that:
- Will check their input for errors
- Will report errors in an informative way
- Will use messages appropriately (i.e. associate them with the correct symbol name)
- Will adhere to the de-facto Mathematica interface conventions: proper use of optional arguments, options, option inheritance (as in Graphics → Plot), default option value handling, use of
Automatic, etc. - Have SyntaxInformation
I find that not infrequently I spend more time on making the function user-friendly than developing its functionality.
How do people generally deal with this task? How do you implement error checking and reporting in your packages?
To give an example, take a function as simple a moving average calculator. It is really easy to implement:
movingAverage[vec_, n_] := Mean /@ Partition[vec, n, 1]
But to bring it to the quality of the built-in MovingAverage, it should at least:
- check the number of arguments (precisely 2)
- check the types of arguments (a list and an integer)
- check the values of arguments for correctness (non-empty list and positive integer)
- make sure that all these checks don’t introduce severe performance degradation (such as array unpacking, which can even be triggered by an inefficient argument pattern)
This also involves the introduction of multiple messages (for each type of error) associated to movingAverage. If we now want to add a movingMedian, we will find that we will mostly need to carry out the same checks and report the same messages. There will be small differences though, e.g. average calculations are feasible for symbolic lists like {1,x}, but not median calculations. So the checks won’t quite be identical. The messages will be mostly identical, but each function must associate messages to its own symbol, which means a lot of duplication.
So if we care about a high-quality interface and high-quality error reporting, we will end up writing considerably more code for this than for the function’s core task. We will also end up with a lot of code duplication, which is frustrating and a maintenance burden. The whole thing ends up being a lot of work and not a lot of fun (which is not very Mathematica-like
)
Are there good ways to simplify these tasks? Option handling also used to be error-prone and frustrating, but the introduction of OptionsPattern[] and OptionValue[] made it much easier.
I can see that there are some built-in tool to ease these tasks, though they are mostly undocumented. One example is ArgumentCountQ, another is Developer`CheckArgumentCount, or some tools in GeneralUtilities` . I would love to hear from others about how they deal with the tasks I described, which such internal functions they make use of, etc.