OKeefe.R.A%EDXA@sri-unix.UUCP (11/02/83)
From: Rechard HPS (on ERCC DEC-10) <OKeefe.R.A at EDXA> The assert/retract question having generated more fury than findings, I suppose it is silly of me to ask another "imperialistic" question. Given that the only public response to my proposed design principle for what should be an error and what should fail was an explicit repudiation of consistency in favour of "user convenience", notoriously ill-defined, it's not just silly, it's downright stupid. But the question is an important one, so I boldly ask What should Prolog do when it detects an error? Let's keep separate the debate over whether "X is 1+a" should just fail (my view) or give an error message. There are some things where we are agreed that error messages should be produced, say "X is 1+_" (instantiation fault) or "X is 2^300" (overflow). And a Prolog program may detect errors of its own. Let's also keep syntax errors for another debate, and concentrate on these run-time errors. There is no denying that the error messages in DEC-10 Prolog are rather unhelpful. The way that compiled code says "! mode error" and doesn't say what predicate, and just keeps running, is a real pain. I have put a wee bit more thought into the error messages in C-Prolog 1.4.EdAI, but not much, and the fact that several of the errors turn tracing on is less helpful than I expected. Chris Mellish put a lot of thought into the error messages of PDP-11 Prolog, which try to say clearly what the problem is and what is a likely cause. The trouble is that the real cause is often something else. But let's keep the design of error messages separate from the actions performed when an error is detected. Clearly, the program cannot continue. Making all errors fail would be defensible, as an error is one way of failing to prove a goal, but I think that failure should be reserved for when you KNOW that you have failed to prove the goal, not for when the machine has turned out to be inadequate. (I repeat, I want "X is 1+a" to fail, but if you want to treat it as an error, and print some sort of message, then you shouldn't make it fail.) One possibility would be to copy Lisp 1.5, and to have an errorset. The Prolog interpreters sold by Expert Systems Ltd. have something like this. I don't remember the details, but it is like PrincipalGoal if_error AlternativeGoal where PrincipalGoal is the goal you are really interested in, but if an error occurs while proving it, the interpreter fails out to the "if_error" goal and tries the alternative. This is quite neat. It is easy to understand procedurally, and it gives you something very like "recovery blocks", E.g. sort(Raw, Ord) :- experimental_sort(Raw, Ord) if_error merge_sort(Raw, Ord) if_error insertion_sort(Raw, Ord). If an error occurs in the AlternativeGoal, it is NOT caught by the if_error. This isn't bad. It has deficiencies rather than problems. They provide another predicate for picking up the name of the error (error names are numbers, I think), so your handler can do different things for different errors, and there is another predicate for signalling an error. But you would in general like to control whether an error message is printed, whether a stack trace is printed, whether the debugger is otherwise entered, and these things have to be done when the error is detected. By the time you arrive in the AlternativeGoal the stack that the programmer might want to look at has gone. The main problem with this scheme is that it is so simple that people will be tempted to use it as a control structure, a means for effecting non-local GOTOs. Something which should help to prevent that would be a means of detecting in advance whether an error is likely to occur. A good example of that is see(File) and tell(File) when the file does not exist. DEC-10 Prolog has a nasty little flag "nofileerorrs" (this is an excellent example of a "feature" put in for "user convenience") which makes these commands fail if the file doesn't exist, in the normal state they produce an error message. There is a library predicate exists(File) which tells if a file exists. (This is built in in C-Prolog.) Instead of hacking the flag, you can write load(File) :- exists(File), see(File), .... seen. load(File) :- \+ exists(File), writef('Sorry, can''t open %t, nothing done.\n', [File]). To handle input-output more generally, I suggest a new system predicate "cant_io(Goal,Reason)" which determines why an I/O command cannot be done, or fails if it can. I am not aware of any predicate like this in any existing Prolog system. In my "imperialist" way, I am asking whether the community think it as good an idea as I do, and if there are improvements to it. The following should be recognised: cant_io(see(File), _) -- openable for input cant_io(tell(File), _) -- openable for output cant_io(append(File), _) -- openable for output at end [C-Prolog v1.4.EdAI has append(File) as a command corresponding to fopen(File, "a") in C. Its omission from DEC-10 Prolog is probably because of the general hostility of Bottoms-10..] cant_io(rename(Old,New), _) -- rename/delete cant_io(cd(Directory), _) -- change directory [C-Prolog v1.2B.EdAI had cd. I have written a "cd" program for Bottoms-10 and it was amazingly painful. Its omission from DEC-10 Prolog comes as no surprise to me whatsoever.] cant_io(save(_), _) cant_io(restore(_), _) ... You get the picture. I am not sure whether cant_io(get0(_), _) should exist or not, because there is already a way of detecting end of file without using errors. Having used Pascal and PL/I, I think it easier to write programs that check for possible problems first than to code error handlers, however congenial the error system. The best way of handling arithmetic overflow is to provide long integers, so that it can't happen. DEC-10 Prolog has a long integer (and rational) package, {SU-SCORE}PS:<PROLOG>LONG.PL . A new Prolog implementation could quite easily do long integers in C, there is a C library "mint" that might be usable. I have seen another Prolog system that handled errors another way. The idea was that a clause like p(X...Z) :- tests..., cause_error(Error). would effectively be replaced by p(X...Z) :- $handle_error(Error, p(X...Z)) where $handle_error(Error, Culprit) :- handle_error(Error, Culprit), !. $handle_error(Error, Culprit) :- ..print message, enter debugger This is not an accurate description, just an outline. When I first saw it, I was greatly taken with it. "Neat!" I though. But I have since come to see its problems. The major problem is that there is ONE error handler for the whole program. An instantiation error in a user query might need one approach, while the same error (even in the same clause) in a part of the program might need another approach. With a single error handler you have to get around that by putting "I am doing X" into the data base. You can guess how much I like THAT. The fact that you can add your own clauses to the error handler is nice. And the fact that they are called in the context of the error is also pleasant, because you can write E.g. handle_error(Error, Culprit) :- writef('! Error %t involving %t.\n', [Error, Culprit]), break, abort. and let the user invoke the debugger to examine the state. There is still a problem here, though. You may want to fail after all, and to do that you need something like an ancestor cut. In general, digging out the information you need to decide can be hard too. And while it is less convenient than if_error, because it is the *program* which decides what is to happen to errors, it still tempts programmers into using the exception-handling mechanism as a control structure. If you have implemented a Prolog system which has some other method of handling errors, please tell us about it. NOW is the time to discuss it, while Prolog is still growing and before we all end up with incompatible InterPrologs and MacPrologs. My suggestion is this. That whenever an error is detected, by the Prolog system or by signal_error(...) in a Prolog, an error message should be displayed on the terminal, and the user should be asked what s/he wants to do. The options should include - enter the debugger - abort to the nearest top level - show the stack history and ask again - let if_error take over and maybe more. If the program was running compiled, entering the debugger could be hairy, but it is something we need anyway. If the Prolog system had to fail out a couple of levels and retry the goal using interpreted code that wouldn't be too bad. In the debugger, the user can say what s/he wants to fail, so if s/he wants the faulted goal to fail, that's not Prolog's responsibility. And the fact that *every* error can be caught by the user and made to do something else should stop programmers using error handling as a control structure. The best candidate I know for the actual handling mechanism is Expert Systems Ltd's "if_error". Oh yes, before anyone shouts at me "EVIL! You MUSTN'T dictate to the programmer what s/he does with error handlers. That's AUTHORITARIAN, and that's the worst crime in the book!" let me point out that in practical real-life programming, other people than the author have to read the program, even maintain it. I'm concerned for those people: if they see something that looks like an error handler, then indeed it should be something that handles errors. If it turns out that programmers really cannot live without a nonlocal goto such as CATCH and THROW, the answer is to provide CATCH and THROW. There is no evidence yet that people do need non-local gotos in Prolog. The old DEC-10 Prolog compiler provided the ancestor cut (which goes part of the way, it is certainly non-local), the current one does not, and in four years of Prolog programming I have never missed it, though I *have* missed nested scopes (as provided in functional languages). The cleanest way of incorporating bizarre control structures is to use another language such as Lisp or PopLog where they make sense.