Errors considered harmful

Well it’s easy to miss things in a thread that’s 120+ posts long and I probably wasn’t always clear on my main issue. But at the risk of making it longer yet…

Having a parallel set of built-ins is possible, but may not be ideal. It’s hard to find built-ins that don’t generate exceptions under some circumstances, e.g., functor, length, call, atom_chars, etc. to name but a few. Another less than ideal possibility proposed by O’Keefe in a submission to early standards discussions, is an environment flag as I noted earlier in the thread. (Unfortunately, IMHO, this didn’t make it into the standard.)

There’s probably too much history to really fix this in a way that will get wide-spread agreement. My main point is that exceptions are a problem for applications that search (one of the original motivations for Prolog) because the search is terminated before a possible solution can be found (i.e., it’s incomplete) unless the exception can be converted to a failure. And that can be messy and/or inefficient, requiring additional tests/guards or a catch wrapper. (Much easier, IMO, to convert failures to exceptions with a final clause or wrapper.) But I do appear to be in the minority on this issue.

Search is a good example …

I would have thought that it should be possible to specify a search algorithm without exceptions occurring …if everything is in the happy-case region (including where failure ought to happen)

Daniel

It seems that there are two separate issues – whether “primitives” should throw exceptions and whether errors in general are a good or bad thing.

As an example: a parser. If a parse fails, false is not a helpful response. A full handling of errors could be quite a bit of work and requires threading an error return through the code(*); but a fairly simple technique is for a failure to throw an error with some indication of how far the parser has advanced.

So, in general, I like to use exceptions as a way of providing a more graceful failure, with some indication of what went wrong.

The problem is that there are a number of situations where it’s a question of interpretation as to whether a failure to conform to the contract (to use @grossdan’s term) should result in failure or an error. For example, X is 1+a, X is 1+[], X is 1/0 generate three different kinds of errors. That’s helpful; and the chances are good that an error indicates a programmer error. But what about 1=:=2 vs 1=:=[] … is the latter an error or should it be treated like 1=[]?. If Prolog were a statically typed language, then 1=:=[] would fail to compile (assuming that =:=/2 is marked as taking numeric arguments).

Maybe the problem can be solved by having a wider variety of “equals” predicates, although we already have quite a few (=/2, ==/2, =:=/2, =@=/2, ?=/2, subsumes_term/2, single-sided unification, and probably a few others I can’t think of right now). The fact that we have all these “equals” predicates is an indication that the problem is difficult to solve. I don’t have an answer; and there may in fact be none. (Lisp has its own proliferation of “equals”; Python allows defining a __eq__ method to override the default meaning of == as equivalent to is; JavaScript is incoherent and inadequate.)

(*) If the parser is written with the Extended DCGs, then it’s relatively easy to add an “accumulator” for the error information, but still a bit tricky to stop the parsing at a specific point.

When and whether an application defines an error to be an acceptable result is a decision that I’m happy to leave to the programmer. And, as in other programming languages, using exceptions to perform deep exits with useful information is a legitimate usage. My main issue is with primitives (and, to a lesser extent, libraries) that throw exceptions.

I struggle to find an example of an application where a primitive exception is a useful application outcome, e.g., a parser error indicating a type or domain exception in some primitive is hardly helpful. If you accept that, then unless the input is restricted by requirement, additional code is required, either in the form of guards or a catch/3 wrapper.

In effect, guards preempt exceptions by turning them into failures. They’re usually cheap (is_list might be one exception), but impose an additional load on the programmer (unless you consider this useful documentation for the guarded primitive). They’re also redundant in the sense that they’re performing the same checks as the primitive (if only it would fail in the same way).

Catch wrappers can be relatively expensive when compared to the execution cost of the wrapped primitive (ignoring primitives with side-effects, e.g., IO) as well as being rather hard to read (IMO).

So the end result is that I’ve added clutter to the code and impacted performance (to some degree) to achieve the desired application result, even if that result is an error with more useful information, e.g., your parser example.

In my experience, about the only time a primitive exception is actually useful is during development when code is buggy or incomplete. Silent failures due to programming errors are common then, and only some of them are circumvented by primitive exceptions. I would suggest that primitive “noisy failures”, perhaps enabled by the debug flag, would be just as useful while debugging.

Summarizing, exceptions just punt the problem up to higher level code, either as alternative clauses or error handlers. One way or another, turning them into something “callable” imposes a penalty.

Also, purely for aesthetic reasons, code which has a declarative semantics is IMO more elegant and easier to understand. Errors have no declarative semantics, whereas failure does. For example, if X is 1+a fails, it means there is no X which holds for this goal. So I think it’s unfortunate that non-declarative semantics have been built in to the lowest levels of Prolog systems. (Instantiation errors are a whole other topic.)

I have no expectation that this discussion will lead anywhere but, as I said at the onset, it’s more of a pet peeve than a showstopper. A global flag which affects primitive semantics seems unworkable, although perhaps a per-module flag is conceivable. A parallel set of some of the primitives, possibly used with in-module goal expansion, is another approach but seems like a lot of work.

The use case of primitives causing errors is this: The programmer encounters the errors while developing, and is thereby notified that handlers should be coded to deal with the error.

A more robust mechanism would be the compiler requiring, to program the error handling into the program, as a language like Haskell often does. Imagine Prolog replaced is/2 with is/3 is(Result, Expression, ErrorHandler). Then ErrorHandler could be passed as throw(is_failed(Expression)) or fail, but you would not be allowed to ignore the error.

I hesitate to prolong this discussion yet again as I’m largely repeating myself, but …

I dislike (some) exceptions from primitives because they’re non-logical, contribute to code clutter and can impact performance, and there is no Prolog level workaround. (catch/3 is particularly expensive.) If they’re mainly a mechanism needed for program development, as you seem to suggest, I should be able to disable them them, just like debug mode, so some exceptions (e.g., type and domain errors) become failures, as O’Keefe suggested in one of his pre-standard discussion papers. (There are errors that the application program can’t deal with, e.g., running out of memory.)

But nothing is likely to change, so it’s just a “flaw” (IMO) in Prolog implementations I’ve learned to live with. Enough said.

All built-ins except for the ISO predicates may be redefined without warning. You do need to do so before calling though. After calling they are imported from system into the module where the call was made.