How to handle errors in general and in the Prolog world is high up on my list of things to figure out, and the discussion is a great jump off point for this [1].
More specifically, I wonder what client code should expect from a predicate to return true of false only, and when client code is also expected to handle errors thrown by a called predicate or its subclass.
There is also the whole world of optional where errors are simply packaged into a structure and returned without exception calling – how does this mesh into the Prolog mindset of programming.
In my code I make use of exceptions for debugging – when guards that must succeed fail, which are, however, compiled away, when not in debugging.
And i selectively made use of optional – so my code is currently not consistent when it comes to error handing – which needs to be worked out – once i find a usable error handing strategy.
In my mind there are only two categories: failure you expect, and failure you didn’t expect. The latter is a bug if it occurs, because you should have anticipated it. So if you expect a failure to occur, then when it occurs you decide what to do: pass the failure up; throw; try an alternate solution.
For reasons of efficiency, (not choicepointing) you might prefer some small high-performance function to throw rather than fail if it’s almost always expected to succeed.
If you didn’t expect it, that is, if it’s a bug, you decide what’s the most useful way to deal with it: start the debugger; abort; or maybe it’s critical execution and you want to gracefully escape the computation and resolve some dangling issues.
Thank you for your insights. It seems to me that there is an additional category of failures such as when a precondition fails – in particular preconditions that ensure type safety.
Currently, the approach i am taking is to throw an exception while true and false as reserved for expected truth values according to domain semantics.
Although, this is not clear cut, and sometimes for efficiency I do throw an custom exception – which has semantic meaning – in lower level predicates that thereby signal failure in a higher level predicate where the exception is caught and turned into failure.
Can these be implemented by adding assertion/1 goals to a clause? (That is, not limited to type checks, but to other preconditions, such as “non-negative integer” or “is a balanced red-black tree”)
And if you have pre-conditions, I suppose you can also have post-conditions. Eiffel has these (design by contract) but this doesn’t seem to have become popular (but neither has Prolog become popular …)
There is a book, Elements of Programming by Stepanov and McJones (try to ignore the C++ connection). This book makes heavy use of preconditions. What those are is already explained in the first section (page 13). What I find curious is that the authors give a formal description of what preconditions are, and use mathematical language to define them. Still, from what I can see (might be I am missing the big picture!), they do not provide a concrete mechanism for enforcing those preconditions. (Edit: they also define postconditions, see for example page 22).
My take on this: ideally, preconditions would be enforced as early as possible and serve as a guideline to the programmer. Asserting all preconditions at compile time is impossible and doing it at run time is prohibitively expensive and just silly (at least for the kind of preconditions used in this book). Entering a procedure without meeting the preconditions means the results are undefined.
As a programmer, I see exceptions as exactly that: exceptions. I refuse to incorporate them in the control flow. I can throw if I am interacting with a resource which is out of my direct control (an API I must use, for example). I can catch specific exceptions so that I can log the error and give up or in very rare cases, retry (a retry mechanism is usually implemented at a lower level, be it in the filesystem or in the communication protocol etc). I want exceptions to catastrophically stop the execution of the program in any “unexpected exception” case. The exceptions thrown by SWI-Prolog built-ins are always the kind of exceptions that should just interrupt your program and force you to deal with them on a higher level (be it by debugging or restarting or going into a well-defined failure state). They happen because your preconditions were not met.
Code of the variety that you see in the wild, be it Java or Python, where exceptions slowly creep into the logic of the program is obnoxious and almost impossible to reason about. Maybe I am jaded but from experience, handling code that is desperately trying to “handle errors” is not something anyone should be wasting their time on.
I decided to use the predicates provided by the error library to raise type, domain and other errors – so, indeed its not only about type checking.
Also, i wrapped it all into term_expansion so that these checks can be selectively enabled during development and disabled during deployment – given the cost of these checks during runtime.
wrap_predicate/4 can be used instead of term_expansion. (The rdet pack was changed from using term_expansion to using wrap_predicate/4, resulting in a big simplification (the changelist shows 27 added lines and 210 deleted).