Subject: Re: [SWIPL] Type error vs. silent failure

From: "Richard A. O'Keefe" <[email-masked]>
Date: Mon, 29 Oct 2001 10:27:38 +1300 (NZDT)

Gernot Salzer <[email-masked]> wrote:
	Is there any rational[e] behind
	   nth1(a, [a,b,c], X).
	failing silently and
	   length([a,b,c], a).
	giving a type error?
	
	The question is ... what I can sa[f]ely assume
	as a programmer when writing a portable program (as much as possible).
	
The behaviour of nth1/3 in this respect differs from Quintus Prolog,
where nth1/3 starts out

nth1(Index, List, Element) :-
    (   integer(Index) ->           % are we finding Element?
	Index >= 1,
	N is Index-1,
	nth0i(N, List, Element)
    ;   var(Index) ->               % or are we finding Index?
	nth0v(List, Element, 1, Index)
    ;   should_be(integer, Index, 1, nth1(Index,List,Element))
    ).

Why does QP do that?  Because when I implemented if_error/3 signal_error/1
(having invented them in 1984 and having told the BSI about it, who then
proceeded to ignore that document as they ignored so much that I wrote them;
and then they put them back in when they thought they could get away with
it, under the most ghasstly inappropriate names catch and throw) at Quintus
(I shall never forgive Quintus for yanking my paper about the implementation
method from that conference) I went through all the libraries adding all the
error checking and reporting code I could.

Back in Edinburgh in the late 70's, David Warren used to say that a
"totally defined semantics" was one of Prolog's strengths, and it was
regarded as a *feature* of DEC-10 Prolog that length([a,b,c], a) would
quietly fail, because indeed that query _doesn't_ have any solutions, and
we can know this for certain.  Error messages were reserved for situations
where the computation could not be completed.  It was for this reason that
DEC-10 Prolog regarded all predicates as defined (even if the definition
was 'fail') so that calling an "undefined" predicate just failed quietly.

Paying customers didn't like mistakes in their programs showing up as
quiet failures, and Kennita Watson, our technical writer, didn't like it
much either, as she was trying to learn Prolog from our manuals.  So from
quite early on, Quintus added a fair bit of 'error' reporting to the core
built in predicates.

I was responsible for
 - there BEING an exception handling facility in Quintus Prolog
   (as noted above, I was also the first to recommend to the BSI committee
   that there should be such a facility in the standard, and the first to
   provide a detailed proposal, which I *still* think was better than what
   they ended up with)
 - a formal specification of the exception handling facility (I only had
   to add 5 lines to the formal specification I wrote earlier, available as
   a University of Auckland technical report, and determinedly ignored by
   the BSI and ISO committees, most of whom never even bothered to look at
   it, and the one who said he had looked at it quite obviously hadn't
   because he claimed the model was in Pascal, when it was in a pure
   functional notation)
 - the 16-way classification of errors.  The top level of the classification
   was
	FAULTS	- possibly meaningful goals that cannot be correctly
		  handled by the system, for reasons including limitations
		  of the system (no rational numbers, or some arbitrary
		  limit on atom sizes, or floating-point overflow) and
		  errors in the operating system (lost access to a file
		  because a remote node went down, for example)
	ERRORS - commands with side effects that could not be carried out
		 for some reason due either to a flaw in the program
		 (wrong argument type) or the state of the world (no right
		 to access a file)
	FAILURES - things that according to a not very terribly strained
		   semantics are definitely false, but are so very strange
		   that they are almost certainly due to program errors.

   For example, "X is a" is a domain failure (the atom 'a' is not in the
   domain of arithmetic expressions, there is no such X, so the query is
   false, but it's probably a mistake) but "tab(a)" is a domain error
   (because the 'tab' action could not be carried out).

   Faults are things that are the system's fault, not the programmer's.
   The goal makes sense, and might even have solutions, but the system
   cannot tell.  Failures are things that _could_ be turned into quiet
   failures; this occasionally makes sense.

   After I left Quintus, this was "simplified" (that is, made shorter,
   but harder to use effectively) by collapsing errors and failures.

 - designing the exception term scheme (inspired by Common Lisp) to
   provide as much information about the _original_ source of the
   problem as possible.

 - going through all the assembler (Progol), C, and Prolog code in the
   system installing error reporting code.

It's the last point that matters here.  Let me advise you that
 - reporting errors *somehow* is not that hard, but
 - arranging that errors occurring deep inside a computation are
   reported *in terms of the public interface* is harder than you would
   expect, especially in a tail recursive implementation where the original
   goal usually isn't on the call stack any more.

You'll note the line

    ;   should_be(integer, Index, 1, nth1(Index,List,Element))

above.  I wrote a library file library(types) defining

    must_be(Type, Term, ArgNo, OriginalGoal)
	report a type ERROR in argument ArgNo of OriginalGoal
	if Term is not an example of Type.

    must_be(Type,       ArgNo, OriginalGoal) :-
	arg(ArgNo, OriginalGoal, Term),
	must_be(Type, Term, ArgNo, OriginalGoal).

    should_be(Type, Term, ArgNo, OriginalGoal)
	report a type FAILURE in argument ArgNo of OriginalGoal
	if Term is not an example of Type.

    should_be(Type,       ArgNo, OriginalGoal) :-
	arg(ArgNo, OriginalGoal, Term),
	should_be(Type, Term, ArgNo, OriginalGoal).

I intended that these predicates or something like them should eventually
be open-coded.  Quintus, pursuing the wrong definition of "simplicity",
merged should_be/[3,4] with must_be/[3,4], but the calls to should_be/[3,4]
still remain in the library to tell you how it was supposed to be.

Why is the wrong type in a call to nth1/3 a type FAILURE rather than a
type ERROR?  Because one can reasonably argue because we can tell without
any doubt at all that

	   nth1(a, [a,b,c], X).

has no true instances, it's false.  One should be able to throw a switch
somehow have and should_be/[3,4] either succeed (if Term is a Type) or
quietly fail (if Term is not a Type); this switch must not apply to
must_be/[3,4].  What about length([a,b,c], a)?  That should be a type
FAILURE too.

What can a portable program rely on?  Only what is in the ISO Prolog
standard.  That standard does specify what length/2 should do; it does
not, if I recall correctly, say anything at all about nth1/3, not even
that it exists.  So a portable program doesn't care what nth1/3 does,
because it can't use it.

SWI Prolog is an amazing piece of work done on the smell of an oily rag.
It takes a lot of work to go through a library and bring it up to
"industrial strength".  Since it is open source, perhaps one of the ways
in which people might contribute is in adding error reporting code to
library predicates.