[comp.lang.prolog] error handling

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