Interesting discussion on Picat extensions and cuts and I’d like to add my own personal pet peeve with “standard” Prolog.
I don’t think there would be much opposition to the topic title. After all (unhandled) errors rip control away from the programmer and terminate the search for solutions. There might be perfectly valid solutions produced by backtracking but they never get to see the light of day. So one of the objectives of debugging a program, in addition to ensuring that successful execution satisfies the program specification, is to remove the possibility of “unexpected” errors, since premature termination is usually not specified as a requirement.
So how does the programmer accomplish this? By adding more code. This usually takes the form of additional guard filters that cause a failure before the call to the predicate that potentially generates the error. The other alternative is deal with errors after the fact by using catch/3
. Either way, the programmer has to bear the burden when predicates are called which result in something other the allowed true
(continue) or false
(fail and backtrack) allowed in logic programs.
Now there are a few cases that are legitimate errors, but they should be confined to circumstances where continued execution isn’t possible or recommended. Such circumstances include running out of memory or possibly some detection of a non-termination condition. But most of the so-called errors generated by builtin predicates don’t fit into this category.
I would argue that if a predicate is called that cannot produce a successful binding for the logic variables in the head should just fail. This includes type errors, domain errors, existence errors, permission errors, and the like. Failure indicates there are no possible bindings of the head variables that would result in success (result = true
). And failure allows alternative choices to be tried without necessitating additional guard predicates.
Instantiation errors are a different animal. Failure (result=false
) isn’t appropriate because there might be some binding of the head variables that would succeed. But neither is success (result=true
) because at some future point a binding could occur which would have caused the predicate to fail. So the result should be conditionally_true
which is really just a constraint. One way of implementing this would be a global hook, like user:exception
, which would determine the fate of instantiation errors: either continue with possible constraint applied or fail (the default).
I’m anticipating some opposition to this idea from those who use errors to provide diagnostic information to the debugging process, since failure does not provide such information. In this context, I’ve also seen the term “unexpected failure” described as a problem. I really don’t understand how failure can be unexpected in a logic programming langauge, where success and failure are the only two results allowed in the foundational theory. IMO the real problem is unexpected errors. If, for any given predicate, failure is not an option, then there should be an explicit last clause that handles “everything else” and succeeds.
To compensate for the loss of diagnostic information, I would propose that when in “debug” mode any predicate that currently generates an error with diagnostic info would fail and output identical information using the standard message mechanism. But there should be a way of disabling this, since such failures may now be considered normal behaviour.
Going down this path results in something that definitely isn’t “standard” Prolog, whatever that means these days. Perhaps it’s a dialect or perhaps it’s a different programming language like Picat. But I think it results in cleaner, more obvious code. And it should be compatible with any Prolog code that uses (now redundant) guards to eliminate unexpected errors. It won’t necessarily be compatible with code that uses catch/3
(expected failure?) unless the handler in the catch is fail
. Other use cases will have to implement handlers as alternative clauses.