pereira@alice.att.com (Fernando Pereira) (10/23/90)
In article <1328@n_kulcscs.kuleuven.ac.be> bimbart@habakuk.cs.kuleuven.ac.be (Bart Demoen) writes: >Following the discussion on arg/3 ... > > The standard should specify how a builtin predicate can be called 'correctly' > and the effect of such calls (most 'correct' calls succeed, but there is nothing > against deciding that calling arg/3 with a negative first argument is not an > error and defining the behaviour as fail) > ALL other calls should silently fail if debug is off (the behavior of a Prolog > system when debug is on, is not standardized) > >This is sensible for running applications - they typically have debug off ... I strongly disagree with this. Running applications *do* have bugs, and silent failure will make those bugs extremely difficult to report. Silent failure is *never* a reasonable response to an error situation, not withstanding the sloppiness in that regard of most Prolog systems (including some I had something to do with...). The reasonable response is to raise an exception. Such exceptions might be caught by handlers that then proceed to fail silently, but by default uncaught exceptions will arrive back at some top level and be reported. That way, bugs that cause builtins to be called with incorrect arguments will produce error reports directly related to source of the problem. Fernando Pereira 2D-447, AT&T Bell Laboratories 600 Mountain Ave, Murray Hill, NJ 07974 pereira@research.att.com
bimbart@habakuk.cs.kuleuven.ac.be (Bart Demoen) (10/23/90)
Following the discussion on arg/3 ... As people might know, the current draft standard for Prolog specifies under which circumstances a call to a predicate is in error and what has to happen then. Error messages were considered too environmental and so are not included in the current draft. But still, some error recovery is currently proposed: a goal in error must give rise to a replacement of the goal by a call to the builtin predicate exit_block/1 with as argument the term error(A,B) where A is an atom identifying the type of error (defined by the standard), and B being implementation defined. This seems not common practice: common practice seems to be give error message and then fail or enter debugger. Also, the current draft seems miles away from (one of) the design principle(s) by R. O'Keefe. I once proposed the following to ISO - but it was turned down (without voting if I remember well) The standard should specify how a builtin predicate can be called 'correctly' and the effect of such calls (most 'correct' calls succeed, but there is nothing against deciding that calling arg/3 with a negative first argument is not an error and defining the behaviour as fail) ALL other calls should silently fail if debug is off (the behavior of a Prolog system when debug is on, is not standardized) This is sensible for running applications - they typically have debug off ... This is sensible for optimising compilers and for program analysis. This is close to current practice (in some systems messages can be turned off, so that only failure remains) You can have error messages in debug mode - the only mode they make sense in. Another proposal is: let there be a warn_on and warn_off mode; the standard should then require in warn_on, all erroneous calls to builtin predicates must be reported and also fail in warn_off, all erroneous calls to builtin predicates only fail Perhaps there should be 3th mode in which the exit_block/1 mechanism is used. How do people feel about this ? Bart Demoen
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (10/23/90)
In article <1328@n_kulcscs.kuleuven.ac.be>, bimbart@habakuk.cs.kuleuven.ac.be (Bart Demoen) writes: >The standard should specify how a builtin predicate can be called 'correctly' >and the effect of such calls (most 'correct' calls succeed, but there is nothing >against deciding that calling arg/3 with a negative first argument is not an >error and defining the behaviour as fail) >ALL other calls should silently fail if debug is off (the behavior of a Prolog >system when debug is on, is not standardized) Any clear principle is a long way better than none. I would rather use a system defined in accordance with Demoen's rule than something bodged together inchmeal. However, this has to be said: failure is NOT a good way of reporting exceptions. In Prolog, 'fail' is *normal* behaviour for many predicates. For example, if I call read(a) and the next term in the current input stream is "b. ", the result is properly a failure. How would you regard a programming language where the error reporting behaviour for all the math functions was to return 2.0, so that sqrt(-47.0) = 2.0, tan(pi/2) = 2.0, arcsin(29.0 = 2.0, and so on? That's a pretty fair analogue of making Prolog EPs report errors by failing quietly. > This is sensible for running applications - they typically have debug off ... It is necessary to distinguish clearly between two things: signalling in some program-detectable fashion that an exception has occurred, and reporting in some human-comprehensilbe fashion which exception has occurred (and perhaps providing the opportunity to intervene). 'debug' refers to HUMAN interfacing and intervention. I've forgotten which famous computer scientist it was, but switching off error detection in shipped applications has been described as "wearing a life jacket while you're in the harbour, but throwing it away when you're out on the open sea." It is *precisely* running applications, where the end user has no access to the source code, where it is *especially* important that exceptions should be signalled to a program as I recommended in a document sent to the BSI committee in 1984 (signal_error(ErrorTerm) and if_error(Goal, ErrorTerm, Handler) -- I'd now put the ErrorTerm first in if_error/3, and it would of course be nearly as stupid to use ATOMS as error terms as it would to use integers). That proposal was borrowed more or less directly from Ada and ML. The scheme in C++ is similar. Detecting exceptions in EPs and reporting them _to the program_ is not a debugging issue! > This is sensible for optimising compilers and for program analysis. I would point out that Ada and PL/I have run-time error detection with errors being signalled _to the program_ and it doesn't seem to hurt their optimising compilers much. It would hurt a Prolog optimising compiler less than error=failure would. Consider p(X) :- see(X), q, fail. p(X) :- close(X). If exceptions are signalled, an optimising compiler can assume in the second clause that X is an atom (or a string; at any rate that it is ground). if errors are confounded with failure, a compiler does not have this opportunity. It is easy to construct other examples like this. > You can have error messages in debug mode - the only mode they make sense in. Signalling errors _to the program_ does not involve error messages. Ada and C++ have exception handling without having error messages. -- Fear most of all to be in error. -- Kierkegaard, quoting Socrates.
bimbart@hera.cs.kuleuven.ac.be (Bart Demoen) (10/25/90)
some reactions to <4052@goanna.cs.rmit.oz.au> ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) <11521@alice.att.com> pereira@alice.att.com (Fernando Pereira) <4055@goanna.cs.rmit.oz.au> (Richard A. O'Keefe) > "wearing a life jacket while you're in the harbour, but throwing it away when > you're out on the open sea." I agree that's is pretty stupid thing to do, but to have quiet failure of a builtin predicate in error, is not like having nothing at all > Running applications *do* have bugs, and silent > failure will make those bugs extremely difficult to report. silent failure is not useless: if the mechanism of reporting errors (to the running application) is by exit_block/1, the application has to be programmed that way; if the the mechanism is silent failure, the same holds; it is the difference between writing ... , block(arg(N,Term,ArgN),error(Type,_),handler(Type)) , ... and writing ... , (arg(N,Term,ArgN) -> true ; handler(...)) , ... and whether one looks nicer or more structured than the other, is not the point, the point is that silent failure is of use; and whether the application writer wants to exploit silent failure always or not, is her business, while if the standard requires all errors to behave in the longjmp fashion, the programmer must defend herself against such a thing continuously perhaps the issue is not which mechanism is better, but rather which errors should have which recovery mechanism; to decide this, a classification like "disasters, faults,errors,failures" (yes, we keep a copy of such stuff) might be more helpful than the current classification of the draft standard, which treats all errors equally - with the exception of "system error" to which the Prolog processor is allowed to react in an implementation dependent way > How would you regard a programming language where the error reporting > behaviour for all the math functions was to return 2.0 good joke - who implemented that ? > That's a pretty fair analogue of making Prolog EPs report errors by > failing quietly. it enforces my belief that not all errors (as descibed in the draft right now) should be treated in the same way > The reasonable response is to raise an > exception. Such exceptions might be caught by handlers that then proceed > to fail silently, but by default uncaught exceptions will arrive back > at some top level and be reported. it is not really possible to enforce silent failure with the current draft, unless by wrapping every call to a builtin predicate in a call to block/3, which is very inconvenient and inefficient; still, silent failure is in many circomstances very useful and there should be a way to get it at a low price the current draft looks to me like a compromise between the people who would ideally like all erroneous calls to builtin predicates to be replacable by any other (user defined) call (without unwinding the stack as exit_block/1 does) and those people who believe that such a thing is (almost - to be careful) not implementable in an efficient way (in debug mode it is of course possible) Bart Demoen
micha@ecrc.de (Micha Meier) (10/25/90)
In article <1328@n_kulcscs.kuleuven.ac.be> bimbart@habakuk.cs.kuleuven.ac.be (Bart Demoen) writes: >... But still, some error recovery is currently proposed: a goal in error must >give rise to a replacement of the goal by a call to the builtin predicate >exit_block/1 with as argument the term error(A,B) where A is an atom identifying >the type of error (defined by the standard), and B being implementation defined. I'm still not sure if this approach is better than nothing at all or not. exit_block/1 can of course be used to catch the error somewhere in the top-level loop, emit an error message and abort. The question is, why such a complicated mechanism to get so little? The problem with exit_block/1 is that you have to have a corresponding block/3 call in order to catch it, and so if you want to something different that just abort, you'd have to wrap every call you expect may make an error into a block/3. So, e.g. if you want an undefined procedure to fail, you would write instead of p(X) block(p(X), Something, fail) in *all* places where p/1 might be undefined. One can hardly imagine something more awkward. I have discussed this in my NACLP'89 paper about events. > ALL other calls should silently fail if debug is off (the behavior of a Prolog > system when debug is on, is not standardized) >... >You can have error messages in debug mode - the only mode they make sense in. As a matter of fact, this is not always true. You can have a 'debugged' program running in nodebug mode, and with a new set of data your program does something strange, because some of the builtins just silently failed instead of making an error. We should not support a programming style where there are error messages coming in the debug mode, but the user is happy with them. There are (or should be) always ways to write your program in such a way that you don't get any error messages even in debug mode, and then emitting warnings or error messages always means something significant, even in nodebug mode. A silent failure is a nightmare. --Micha -- E-MAIL micha@ecrc.de MAIL Micha Meier ECRC, Arabellastr. 17 8000 Munich 81 West Germany
alberto@cs.umd.edu (Jose Alberto Fernandez R) (10/26/90)
In article <1804@ecrc.de> micha@ecrc.de (Micha Meier) writes:
I'm still not sure if this approach is better than nothing at all or not.
exit_block/1 can of course be used to catch the error somewhere in the
top-level loop, emit an error message and abort. The question is, why such
a complicated mechanism to get so little? The problem with exit_block/1
is that you have to have a corresponding block/3 call in order to catch it,
and so if you want to something different that just abort, you'd have
to wrap every call you expect may make an error into a block/3.
So, e.g. if you want an undefined procedure to fail, you would write
instead of p(X)
block(p(X), Something, fail)
in *all* places where p/1 might be undefined. One can hardly imagine
something more awkward. I have discussed this in my NACLP'89 paper
about events.
The problem with this is that we are looking at only two extremes.
Silent fail, the user can't distinguis between good fail and error,
and exceptions, the program is aborted and the exception can be catched
by some handler.
I propose a more simple and flexible mechanism that has been used
already in Prolog. The mechanism used by print-portray.
The idea is that when an error occurs intead of fail, produce an
exception o send a message, the code calls a predicate error_handler/1
defined by the user.
If the call to error_handler fails, then the default is performed
(i.e. write error message or rise exception) otherwise the clause is
executed and after that the predicate fails silently.
With this mechanism the user can control each predicate separately if
he prefers silent on non silent failure. If the user whan to rise a
exception he can call exit_block in its code.
You can also provide with libraries for cprolog behavior of Quintus or
whatever you whant.
Any comments.
Jose Alberto.
--
:/ \ Jose Alberto Fernandez R | INTERNET: alberto@cs.umd.edu
:| o o | Dept. of Computer Sc. | BITNET: alberto@cs.umd.edu
:| ^ | University of Maryland | UUCP: {...}!mimsy!alberto
:\ \_/ / College Park, MD 20742 |
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (10/26/90)
Just to get something minor out of the way first: Please could people write letters of protest or something about the appallingly unprofessional names 'block' and exit_block/1 for the "catch" and "throw" operations? There is *no* excuse for using bad or confusing or misleading names for operations in the standard, especially when the names "if_error" and "signal_error" proposed in 1984 were far less confusing. Even "catch" and "throw" would be MUCH better than "block" (I know what a block is in Algol, C, &c, and I know what blocking an exception means -- it means making sure that the exception ISN'T SIGNALLED until after the block is cleared -- and I know about variants of "block" in lots of languages, and in none of them does it mean HANDLE an exception). "handle_exception" and "raise_exception" would be good names. "catch" and "throw" are what ALS and NU Prolog call them. That's "Prior Art". I rather dislike "catch" and "throw", but *anything* reasonable (even IBM Prolog's err_catch/ and error/) would be better than the incredibly incompetently chosen block and block_exit. Concerning Meir's worries about whether local error-handling is sufficiently powerful: Remember that there is nothing in the standard that says there can't be additional error handling machinery. Suppose someone wants a "global" error handler (I have always been opposed to this). For errors signalled by his own code, nothing could be easier: my_signal_error(ErrorTerm) :- current_predicate(global_handler(_)), !, global_handler(ErrorTerm). my_signal_error(ErrorTerm) :- signal_exception(ErrorTerm). For errors signalled by other predicates, it's again fairly easy: my_functor(Term, Symbol, Arity) :- if_exception(ErrorTerm, functor(Term, Symbol, Arity), my_signal_error(ErrorTerm)). IBM Prolog's "priority prefix" feature can be used to advantage here, or almost any kind of package. So a wrapper has to be added for each EP? So what? The code is repetitive, a query ?- tell('wrappers.pl'), system_predicate(Skel), write('my_'), write((Skel :- if_exception(E, Skel, my_signal_error(E)))), write(.), nl, fail ; told. is all the work you need to do to create the wrappers. Note that a standard can only define the effect of a program that is written entirely in Prolog, using only the EPs defined in the standard. If you call an operation which is not in the standard, anything can happen. So a vendor who had some clever idea about handling could perfectly well provide it, enabling it when you do set_prolog_flag(global_error_handler, my_handler) and disabling it when you do set_prolog_flag(global_error_handler, standard). What's the really important point about global error handlers? Simply that they are only useful for programs where *all* of the source code is under the direct control of a small group. You can't use global handlers in the construction of library packages. The portray/1 mechanism was mentioned. Precisely! portray/1 does _not_ work very well with library packages; the Quintus library defines add_portray/1 and del_portray/1 to work around it. Global error handlers are far worse. And for EPs like arg/3, there simply isn't any global handler that makes sense, precisely because arg/3 is at such a low level that it *means* different things (implements different abstract notions) at different places in a program and so needs to be handled differently. The local error handling scheme I proposed to the BSI in 1984 (essentially the present scheme except for having meaningful names) was a straight steal from Ada (also ML, also a language you won't have heard of called SMALL which had something called "fault procedures"). The point of such a scheme is to enable you to write reliable *components*. The goal is to let people write code which can be _sure_ of handling its own errors (not errors in code that it doesn't know about) without interference. Global handlers make it very hard to write reliable components that handle errors, as a global handler may not only "steal" an exception report, but it may not be able to do anything with it. -- Fear most of all to be in error. -- Kierkegaard, quoting Socrates.
bimbart@hera.cs.kuleuven.ac.be (Bart Demoen) (10/27/90)
In article <1804@ecrc.de> micha@ecrc.de (Micha Meier) writes: > So, e.g. if you want an undefined procedure to fail, you would write > instead of p(X) > > block(p(X), Something, fail) > > in *all* places where p/1 might be undefined. In Vienna (ISO meeting this spring), I used that as one of the arguments against this mechanism: it was discarded ... silently . In article <ALBERTO.90Oct25134549@yamuna.cs.umd.edu> alberto@cs.umd.edu (Jose Alberto Fernandez R) writes: > I propose a more simple and flexible mechanism that has been used > already in Prolog. The mechanism used by print-portray. > The idea is that when an error occurs intead of fail, produce an > exception o send a message, the code calls a predicate error_handler/1 > defined by the user. (and the argument for error_handler/1 is presumably the call in error with its arguments as they were at the moment of the call ?) I was referring to this mechanism in an earlier message, when I said something about an ideal but non-implementable error handling: at the moment an error is detected, the state of the Prolog engine (local stack or heap or whatever) might be inconsistent with 'calling another predicate'; and keeping the necessary information to recover from this inconsistent state might be too costly; that's why 'unwinding the stack' is the compromise: just like failure, it makes the state consistent again, so that a call can be made; Other mechanisms have been proposed - e.g. by Gerald Karam in Ottawa - and they are all very nice in interpreters, but not feasable in optimised systems: after all, ancestor_cut is a 'nice' feature as well, but have you seen it around lately ? Bart Demoen