Subject: Re: handling small numbers

From: "Richard A. O'Keefe" <[email-masked]>
Date: Thu, 11 May 2000 13:36:06 +1200 (NZST)

Paul Sephton wrote:

	Sorry, folks, but I feel quite strongly that in any language EXCEPTIONS 
	SHOULD BE THE EXCEPTION RATHER THAN THE RULE.
	
My first reaction was to say
"This is obviously sensible and true.  Right on!"

My second reaction was "I don't know what he means."

Exception handling is designed as a means of handling exceptional
situations.  That is, situations which are not only rare in general,
but unlikely to occur _at all_ in a run of a well written program.
This is the view taken by most exception system designers, including
me:  setting up a handler (just in case an exception occurs) has to
be cheap, actually raising and handling an exception does NOT have to
be cheap.

Indeed, this is a large part of my objection to the ISO Prolog
names `catch' and `thorw'.  They come from Lisp, where they are 
*NOT* part of the exception handling machinery, but a normal
control structure.

So Paul Sephton and I are in vehement agreement that
exceptions should be rare _events_.

Does that mean that operations that *might* raise exceptions should
be rare in programming language and library interfaces?

Anyone who has been exposed to Java could be excused for getting very
excited and jumping up and down and screaming loudly "YES! minimise
the occurrences of exceptions in interfaces!"

But that's because Java Gets It Wrong.  In Java, you have to explicitly
mention all (well, almost all) the exceptions around, whether you intend
to handle them or not.  Ada and Lisp and Smalltalk Get It Right.

Now most programming languages distinguish only two possibilities for
an operation:  it completes normally exactly once, possibly returning
a value, or it is aborted because it could not be completed.
There are two traditional ways to represent failure:
 - the C way = return a special value (-1 for an integer, NULL for a
   pointer)
 - the PL/I way = raise an exception.
The C way is notoriously error-prone.  I remember when Quintus bought
the right to use a certain windowing package, they found it riddled with
failures to check for error outcomes.  We spent more fixing the bugs
than we would have if we had written our own from scratch.

PL/I made a few mistakes.  End of file is not an exceptional condition;
most files, after all, _have_ an end, and if you keep on reading, you'll
come to it.  It's not as if it were any kind of surprise.  But the
basic idea was right, and it's part of Bertrand Meyer's "Design By Contract".

**  An operation has a postcondition which it is supposed to establish.
**  If it can't establish that postcondition, it should report an exception.

Prolog is not quite the same.  Instead of returning once,
the *normal* condition for Prolog is to return zero or more times,
each time with a different proof of a (not necessarily different) result.
In particular, failure is not how Prolog indicates exceptions, it is the
normal way to say that a particular goal has no *more* solutions.

The question about whether something like integer addition should
raise an error or fail boils down to a question about

    "What is the contract of is/2?"

I suggest that the most *useful* contract is along these lines:

    is(Var, Expression)
    pre:  Var is any term (or: Var is a variable or number)
          Expression is a ground term having the structure of
          an arithmetic expression
          Expression has a value, defined by the laws of integer
          arithmetic and/or machine floating point arithmetic,
          as appropriate
    post: Var is unified with a term representing the value of Expression.

When is it appropriate to raise an exception?
When the contract is violated by either party.

   1.  If a precondition is violated (so the mistake is on the caller's
       side).
   2.  If the preconditions are satisfied but the postcondition is
       about to be violated (so the mistake is in the operation itself).

Here's where it gets a bit hairy.

If you have a query (something without side effects),
you *can* justify failing it if the precondition is violated:
"you have asked a question which is so fouled up it has no answer".
David (H.D.) Warren used to promote this as Prolog's "totally defined
semantics".  In particular, goals like
	functor(NewTerm, 3, a)
would quietly fail.  There isn't any NewTerm whose arity (NUMBER of
arguments) is the SYMBOL 'a'.

While this kind of quiet failure could be justified in theory,
it turned out to make life difficult in practice.  When you start a
computation, and 10 minutes later it says "no", and the reason turns
out to be that you got the order of the arguments to foobar/15 wrong,
you really long for better argument checking and error reporting.
However, that's a purely pragmatic point.

The case where exception=failure is really indefensible is when a
POSTcondition violation is concerned.  When an operation whose
precondition is satisfied cannot meet its obligations, something
really serious is going on, and you need to be told not just THAT
the operation can't succeed but WHY it can't.

The dispute between me about Paul Sephton basically boils down to this:

If the contract for is(Var, Expression) were
[1]
    pre:  Var is any term (or: Var is a variable or number)
          Expression is a ground term having the structure of
          an arithmetic expression
          Expression has a value, defined by THE IMPLEMENTATION'S
          PRIVATE RULES for integer and/or floating point arithmetic,
          arithmetic and/or machine floating point arithmetic,
          as appropriate
    post: Var is unified with a term representing the value of Expression.

then handling overflow as failure would be ok.  We might bicker about
whether that was really _helpful_, but we'd agree that an overflowed
computation would be one that _didn't_ have a value, so defined, so the
answer `fail' would in some sense be right.

If, however, the contract for is(Var, Expression) were
[2]
    pre:  Var is any term (or: Var is a variable or number)
          Expression is a ground term having the structure of
          an arithmetic expression
          Expression has a value, defined by STANDARD AXIOMS FOR
          INTEGER ARITHMETIC IN Z and/or PLATFORM-DEFINED axioms
          for floating point arithmetic, as appropriate
    post: Var is unified with a term representing the value of Expression.

that would be a different story.  (Note that I said platform-defined
axioms for fp; C Prolog and (for some years) Quintus Prolog had their
own floating-point model, which turned out to be a *really* bad idea,
because ours was worse...)  Perhaps we would still disagree about this
case.

I contend that if one wants to write portable programs, the contract
for is/2 *has* to be [2] (or better), not [1].  (Integer arithmetic is
rather fuzzy in places in C, and that's a real pain.)

Of course, the ISO standard has something to say about this,
but the ISO standard is not my idea of good work, so I shan't quote it.