Errors considered harmful

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.

You can use conditional compilation, here is a SICStus example, it switches on/off a first clause. But I guess you can also use it for last clauses. And also automatize that even the switch on/off is automatically generate from something more higher level:

      %% Only need environ/2 at compile-time for conditional compilation
     :- load_files(library(system), [when(compile_time), imports([environ/2])]).
     
     :- if(\+ environ(optimize, true)).
     
     %% This clause does some expensive sanity checks. Disabled when building
     %% an optimized version.
     foo(X) :-
        \+ valid_x(X),
        throw(invalid_x(X)).
     
     :- endif.
     
     %% This clause is always present.
     foo(X) :-
        do_x_things(X).

Invoking the SICStus development system with an option -Doptimize=true, to set the system property optimize, and then compiling the above code will ensure that the first, sanity checking, clause is not part of the foo/1 predicate. Invoking the development system without such an option will ensure that the sanity checking clause is part of the foo/1 predicate.

https://www.fi.muni.cz/~hanka/sicstus/doc/html/sicstus/ref_002dlod_002dcnd.html

Conditional Compilation for SWI-Prolog is documented here:

4.3.1 Conditional compilation and program transformation
https://www.swi-prolog.org/pldoc/man?section=progtransform

This isn’t a bad solution for program level code, but the biggest culprits IMO are the numerous builtin predicates that throw errors at the drop of a hat. No matter how you structure it, the only escape for the programmer is adding more code.

For application software “errors at the drop of a hat” are great. You avoid writing code. I might be missing something of course. But the idea is that you don’t have compile-time type checks, and you don’t want to religiously check the arguments of the predicates you write, so instead you use built-ins and you hope that if you don’t see errors, you didn’t use the predicate in a way that you didn’t mean to.

But maybe some examples would make everything easier to argue one way or the other.

1 Like

This sounds all kinds of wrong to me. But who am I? I am a poor soul who somehow ended up making a living by reading, maintaining, and sometimes writing code, the kind that has been touched by an unknown number of people and will probably be running somewhere, completely forgotten, until it breaks and someone has to fix it. People like me love their errors and exceptions. They are, in practice, always (yes, always) caused by programmer errors. So they are, you know, errors.

Disclaimer: I don’t use Prolog for the code that I share with others. For this, I have to use boring, verbose, well-understood procedural languages like Java and Python, and more rarely, whatever was in vogue when it was written. I sometimes use Prolog when I need results instead of code. So I write it in a hurry, and again, “errors at the drop of the hat” are exactly what I want. (Are they what I need though? I wish I had time to ask myself those kinds of questions… :wink: )

1 Like

No that’s not the idea at all. Check the hell out the code at compile time; errors at that time are perfectly acceptable. But Prolog is a dynamic and typeless language based on a formal logic that pays little little attention to the meaning of a term. So you only discover the actual value of a term at runtime. What I’m proposing is that the program have the option of dealing with expected errors with simpler code.

Lets not confuse errors and exceptions with bugs. A bug is something that causes a program to not behave as specified. The kind of errors I’m talking about are those sanctioned by the system to be considered normal semantics. Errors (as currently defined by SWIP builtins) may or may not be bugs; that’s for the program to decide. The problem is it never gets the chance unless it adds additional “defensive” code every time it calls one of those predicates.

Are you sure? I think what you really want (need?) is diagnostic information that enables you to fix bugs. I don’t think I’m proposing anything that precludes that possibility.

I am not confusing errors and exceptions with bugs. I am just saying that as a software practitioner, in my limited experience, exceptions are invariably caused by human mistakes. I don’t like calling them bugs, they didn’t creep into my code when I was looking the other way :slight_smile: those are mistakes I made, or someone else made, but I need to fix. They are not always programming mistakes, often the mistake happened earlier, at the specification stage, or is hiding between components of the system. But still, mistakes.

Again, difficult to talk about it without concrete examples, but in my approach to writing Prolog, I avoid a lot of code by knowing that there is a high chance I get an error if I mess up.

Not sure about checking Prolog code at compile time, how do I do it?

As for errors, they are by definition unexpected?

I suspect we are talking about different things altogether. For example, if an error is expected, why not just not cause the error to start with? So I guess I am totally missing your point.

The SICStus naming is probably a misnomer. In modern terms it would be an assertion. Many programming languages provide assertion mechanism, which can be switched off. But I dont know whether the SWI-Prolog assertions can be switched off via command line:

assertion(:Goal)
https://www.swi-prolog.org/pldoc/man?predicate=assertion/1

Java has a command line option “-disableassertions”. But there are also tons of books about Java. Maybe the problem is that there are not enough Prolog tutorial that show the important secrets of Prolog. Like a tutorial the Super Power of Prolog is missing.

Not sure, but a distinct possibility on any Internet discussion.

And how exactly is that accomplished? For example:

?- N=fred, number_chars(N,Cs).
ERROR: Type error: `number' expected, found `fred' (an atom)
ERROR: In:
ERROR:   [11] number_chars(fred,_3042)
ERROR:    [9] toplevel_call(user:user:(...)) at /Applications/SWI-Prolog8.3.15.app/

So to avoid this error I typically have to add guard code to cause it to fail:

?- N=fred, number(N), number_chars(N,Cs).
false.

So I’m arguing that number_chars should just outright fail given a first argument which is not a number rather than “blowing up”, and save the programmer the trouble of having to “not cause the error to start with”.

So a trivial example with an equally trivial solution, but the suite of builtin predicates is littered with them:

?- sort(fred,X1).
ERROR: Type error: `list' expected, found `fred' (an atom)
ERROR: In:
ERROR:   [10] sort(fred,_5474)
ERROR:    [9] toplevel_call(user:user:(...)) at /Applications/SWI-Prolog8.3.15.app

?- N = fred, X2 is N+1.
ERROR: Arithmetic: `fred/0' is not a function
ERROR: In:
ERROR:   [11] _8680 is fred+1
ERROR:    [9] toplevel_call(user:user:(...)) at /Applications/SWI-Prolog8.3.15.app/

?- findall(X3,42,Bag).
ERROR: Type error: `callable' expected, found `42' (an integer)
ERROR: In:
ERROR:   [14] findall_loop(_3284,user:42,_3288,[])
ERROR:   [13] setup_call_catcher_cleanup('$bags':'$new_findall_bag','$bags':findall_loop(_3342,...,_3346,

For each of these there are perfectly acceptable logical outcomes, i.e., failure. There are no X1's that satisfy the relationship sort(fred,X1), or X2's that result from evaluating the arithmetic expression fred+1, etc. And yes, these are all silly things to try to do, but when you’re building libraries you have no control over the questions users will ask.

As I said in the original post, this is a pet peeve, not a showstopper. But it’s still a real issue IMO.

I think this is another way of implementing guard code, so it still requires the addition of defensive code to turn errors into what I think should be the default, i.e., failure. My main issue with assertions is when is it ever safe to turn them off, however they are implemented. But that’s another topic.

Sorry, I missed this earlier post in the flurry.

I’m not proposing to do, or not do, anything that isn’t currently done. The compiler/loader catches a ton of errors from simple syntax to missing predicate definitions. It also warns you about singleton variables in clauses, etc. which may or may not be bugs. All good stuff.

Not really, the SWIP manual includes lots of cases where if using builtin functions you should expect errors. And just to be clear, I’m talking about errors as formally defined by SWIP, the ones that cause termination of your program, not errors in design or other programming mistakes.

You can make your own library with predicates that
rather fail, than throw an exception. For example:

?- [user].
fumber_chars(N,C) :- number(N), number_chars(N,C).
^D

A “fumber” is a number or else fail. So you can do:

?- N=fred, fumber_chars(N,Cs).
false.

Cool!

I guess that we are talking about the same thing, but looking at it from very different angles. From where I stand, what I see is that the errors that SWI-Prolog is throwing are meant to do exactly that: blow up your program, so that you can catch your design mistakes and your programming mistakes.

Looking at it from my angle, there is nothing worse than silent failure when you infact gave fred to number_chars/2.

But of course I do not develop libraries, I write code in a hurry without thinking about it too deeply. The proverbial code monkey. :monkey:

True. And perhaps even better, I can use goal expansion to rewrite “number_chars(N,Cs)” as “number(N), number_chars(N,Cs)” so the additional overhead is minimal. This is definitely doable but it niggles me a bit to repeat the same checks that the called predicate is doing anyway, just to fail a branch of the the search path so I can try an alternate; just doesn’t feel like logic programming anymore.

So the next problem in taking this approach if finding all the builtin cases that generate exceptions. Unfortunately there is no annotation for exceptions like there are for argument modes, determinism, and ISO compliance. One would think errors are equally, if not more, important. Java, and perhaps others, insists they be in the method declaration; so much stronger than an annotation.

Big Aside: In general I’m looking for ways to restore the ability to read a Prolog program declaratively, i.e., as a piece of logic. There are innumerable reasons (side-effects, red-cuts, argument modes, etc.) why this is hard, if not impossible, but one issue is that the filters (var/1, atom/1, etc. which do not do any unification) do not commute with unification in clause bodies (conjunction). For this reason perhaps, good logic programming style dictates that filters should be placed before any unification, as in the pre-unification portion of a Picat goal (if I’m understanding it correctly). The filters I’m talking about here should really be in the head of the called predicate, but to avoid errors they must be placed before the call. Rewriting hides all this grotty detail, but I really wish it didn’t have to be that way.

For those who might question the desire for a declarative reading, from the preface of “The Art of Prolog”:

A good Prolog programming style develops from thinking declaratively about the logic of a situation.

It’s taken me about 30 years to appreciate the wisdom of this statement.

To each his own, I guess.

Just remember that eliminating “ERRORS” is no guarantee of success, so called silent failures are much more common, in fact normal procedure for logic programming, i.e., every time you have multiple clauses in a predicate, or use an “if-then-else” construct.

And here’s one that caught me by surprise:

?- atom_chars(42,Cs).
Cs = ['4', '2'].

?- atom_chars(f(x),Cs).
ERROR: Type error: `atom' expected, found `f(x)' (a compound)
ERROR: In:
ERROR:   [10] atom_chars(f(x),_775260)

In neither case is the first argument an atom, yet one succeeds and the other “blows up”. I would argue both should fail, but at least if both generated “ERRORS”, that would be consistent.

How?

The rdet package exists to throw an error when a deterministic predicate fails. By using it, I’ve saved a lot of time in debugging.

(*) The package has some side-effects – e.g., it also does an implicit cut. I’ve written some code to allow more nuanced checks (e.g., succeeds at least once or succeeds without any choice points), but haven’t yet published it.

Looks like a useful tool to have in the toolbox. The standard debuggers help but can be a little intimidating, particularly when you’re learning. This package is more limited but focuses on what I suspect is a common issue.

Yes. I think this stuff needs to find a place in the system. I also have a little library that allows annotating individual goals. See my-prolog-lib/det.pl at 188108f67b0c9edc1a0274a4bf0e39d54cd62cdc · JanWielemaker/my-prolog-lib · GitHub

Ideally I’d like VM support for det and possibly semidet validation that comes at (practically) zero overhead.