I have been thinking about a potential source of bugs from catch-all pattern matches and would like to know your thoughts.
Totality is usually a desirable property of a function and the catch-all can conveniently buy us totality. But at what price?
I have been indoctrinated that rigour goes above convenience (think along the lines of: "Once we indulge in the impurities of I/O, there is no redemption.")
I would like to evaluate the trade-offs between convenience for the programmer and a potential source of bugs.
My questions to the community—
- Are there real world examples of bugs caused by catch-alls?
- Do you think that a language extension that disallows catch-alls (and annotations to opt back in at pattern match sites or type declaration) could be useful for certain code bases?
- If this is a potential problem, then can you think of any better solutions a compiler could provide (i.e. that don't rely on an IDE / structured editing) other than disallowing catch-alls?
Feel free to chip in with your 2p (or 2¢), but please only if you have any concrete experience (or compelling theoretical evidence).
Consider the sum type:
data Answer = No | Yes
and the function:
foo : Answer -> String
foo Yes = "Woo-hoo!"
foo _ = "Bother."
Say we need to extend our sum type:
data Answer = No | Perhaps | Yes
However, we forget to handle the new case appropriately in foo
. The compiler is happy, but at runtime foo Perhaps
would evaluate to "Bother."
—with potentially catastrophic consequences.
(Please imagine this happening in a large codebase with several contributors, no single one of whom knows the entire codebase.)
Catch-alls that either a) are simply part of sane denotational semantics or b) just give a slightly improved error message are fine. Otherwise, just document your function appropriately or, where possible, use stronger types.
As an example of a, consider an AST with many different forms of literals (int, float, string, etc.) and a function that wants to count how many applications there are in a given term. For that function, a single catch-all to catch all of the AST constructors that don't have subterms seems fine to me (though, admittedly, riskier than just listing them all out)