I think we need to distinguish the case when a function cannot be evaluated at a point because the arguments are outside the domain of the function and the case when evaluation of the function at a possibly valid point of it’s domains cannot be carried due to external factors. Examples of external factors would be external services not being available or misbehaving and resource exhaustion. I would only consider the latter case of errors true runtime errors, and a prime use case for
result, since such errors are outside the control of the code, and generally should be handled.
What about domain errors then? In general I would tend to classify these as bugs, but there may be cases where it’s safer or more convenient to use
option rather than raising exceptions. Some examples where I would prefer exceptions:
- Division by zero. The code which passed the zero is most likely broken. If the argument is a float, this is easily seen by adding a noise to the divisor. If the program relies on catching division by zero, it will instead receive huge numbers which may invalidate the result.
- More generally all floating and integer point functions which correspond to mathematical functions. E.g. there is a good practise in analysis to carefully specify domains, and if the code is correct it stays within those domains.
- I think the practise of making sure to stay within the domain is preferable for pure functions as far as feasible (see below). A symptom of a
result which should have been an exception is that the users can clearly see something is wrong and terminates the
Error case with an
But what about the danger that the code fails with an uncaught exception? Well, isn’t there an even greater risk that the code just makes a wrong calculation on some parts of its domain? Note in contrast, that true runtime errors can occur no matter how correct the code is, so it makes a lot more sense to force the caller to deal with it.
Still, one reason to use
result for domain errors may be that the structure of the part of the domain on which the function is defined is too complex for the caller to be expected to deal with. I typical example would be conversions and parsers. If inputs come from external sources, it should be checked, but the easiest way to check the input may be to actually try to parse it.
find then? This can be seen as another case of a complex domain, since there is a non-trivial dependency between the container and the search key (though of course one can use
mem to do the check). But another resolution of this case, which I think is more elegant, is to not consider a negative lookup to be an error at all, but to rather expose the map as an explicitly partial function. That is, to let
find return an
option instead of a
result. The justification for this view would be if uncaught
Not_found exceptions tend to indicate that the code it fact needs a representation of a partial function.