[comp.lang.prolog] arg/3

micha@ecrc.de (Micha Meier) (11/12/90)

Mr. O'Keefe seems to be somewhat inconsistent with his arguments. He says

>> O'Keefe further says
>> <I have frequently used
>> <                arg(N, T, A),
>> <                !,              % now we know that N >= 1
>>
>> These are really bad arguments about the semantics of arg/3. If p/3
>> is called with an integer which is possibly zero, there is no
>> need to push the work to arg/3:
>
>Hello Mr Thought Policeman!  Yes, I *can* rewrite my code.  Why
>*should* I?  Why "push the work to" _me_?  Why is it ok to break
>code that relied on a defined property of arg/3?  Especially when
>you consider that I wrote PS/6 and sent it to the net back in 1984
>precisely to make clear what it was that I _wanted_ to be able to
>rely on?

and elsewhere he writes

> ... My favourite example is
>opening files.  If there is an operation "would I be allowed to open
>file F for operations O", then I don't need to handle errors in open/3.
>But then we run up against the need for lots and lots of "would it be
>ok to do X" predicates.
>

Now as far as I can see, the above use of arg/3 is exactly as saying
"would it be ok to call arg/3 with these arguments?". I admit that for
each predicate the border line between "this query is ok and it fails"
and "this is an error" might be subject to personal taste or
well-based theoretical considerations. For example, just recently I've
encountered a program that has something like

	p(X) :- functor(X, F, A), !, ...	% X is nonvar
	p(Var) :- ...				% when X is a variable!
	
which, in my opinion, is similar to O'Keefe's use of arg/3. Certainly,
others may have different opinions, because arity 0 or too high in arg/3
is certainly not the same type of error as a free variable in functor/3.

When we consider built-in predicates as a shorthand for (possibly infinite)
series of unit clauses, the main question to answer is how to handle
type and range errors, if any, because the call
		arg(fred, p(X), A)
just fails, although we want it to raise an exception. Consequently,
we have to introduce types into the error concept, and what these types
are can be subject of lengthy discussions. To me it seems very helpful
if arg/3 does not silently fail neither on negative numbers nor on
other numbers outside of the arity bounds. The argument that this would
break many existing programs can be answered in a way which is similar
to O'Keefe's suggestions about errors - define
	my_arg(A, B, C) :- A > 0, functor(B, F, Arity), A =< Arity.
and replace your arg/3 by my_arg/3. I still think it is not a good
programming style to rely on a certain behaviour when outside of the
arity range. Furthermore, I don't think that "I have used the feature X in my
programs and therefore it should be in the standard" is a sufficient reason.
I also think that if there is a Prolog Policeman out here, it is not me :-)

--Micha
--
E-MAIL  micha@ecrc.de            	MAIL	Micha Meier
						ECRC, Arabellastr. 17
                     				8000 Munich 81
						Germany

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (11/16/90)

In article <2012@ecrc.de>, micha@ecrc.de (Micha Meier) writes:
> Dr O'Keefe seems to be somewhat inconsistent with his arguments. He says
 
> O'Keefe further says
: I have frequently used
:   arg(N, T, A),
:   !, % now we know that N >= 1
: Yes, I _can_ rewrite my code.
: Why should* I?  Why "push the work to" _me_?  Why is it ok to break
: code that relied on a DEFINED property of arg/3?  Especially when
: you consider that I wrote PS/6 and sent it to the net back in 1984
: precisely to make clear what it was that I _wanted_ to be able to
: rely on?

> and elsewhere he writes

: ... My favourite example is
: opening files.  If there is an operation "would I be allowed to open
: file F for operations O", then I don't need to handle errors in open/3.
: But then we run up against the need for lots and lots of "would it be
: ok to do X" predicates.

What's inconsistent?  Where have I said that there is anything WRONG with
"would it be ok to do X" predicates?  All I said that that if you tried to
cover _all_ errors that way you would need a heck of a lot of them.  I know
that because while writing PS/6 I made the attempt to produce a set of
"would it be ok to do X" operations.  (As for file opening, the Quintus
library contains a predicate can_open_file(File, Mode) ...)

> Now as far as I can see, the above use of arg/3 is exactly as saying
> "would it be ok to call arg/3 with these arguments?".

(a) No.
(b) If it were, so what?  I have never said that "would it be ok to do
    X" predicates are a bad thing.  On the contrary, I _like_ them.
    All I have ever said is that IF you try to cover ALL possible errors
    that way, you need lots and lots of them.  

> I admit that for
> each predicate the border line between "this query is ok and it fails"
> and "this is an error" might be subject to personal taste or
> well-based theoretical considerations.

Personal taste won't do.  For built-in predicates it _has_ to be drawn
on clearly specified practically motivated rules.  What you are doing for
your own use may be subjected to your own taste.  When people are ramming
things down other people's throats in a standard, there is no excuse for
relying on personal taste.  As a matter of professional competence, such
decisions should be justified.

> For example, just recently I've
> encountered a program that has something like

> 	p(X) :- functor(X, F, A), !, ...	% X is nonvar
> 	p(Var) :- ...				% when X is a variable!

> which, in my opinion, is similar to O'Keefe's use of arg/3.

This use of functor/3			My example of arg/3
has never been portable			was not portable outside Edinburgh
breaks in good Prologs			breaks in over-zealous Prologs
could be done better with var/1		has no more direct encoding
 which would directly express intent
is apt to confuse			is apt to confuse the thoughtless
is *NOT* "logical"			*IS* "logical"

There are indeed many similarities.  The big difference is that the
behaviour I want from arg/3 falls naturally out of a small set of
separately defensible rules which were not deliberately crafted to
ensure that behaviour, while the behaviour of functor/3 (in *some*
Prologs only) is clearly inconsistent with its logical semantics.

Please don't lay any great stress on my arg/3 example.  I regard that
behaviour as useful, and would be sorry to lose it, but that is not the
important thing.  If somebody can come up with a clear and simple set
of rules for deciding/predicting which errors built in predicates should
report, and that set of rules demands that arg(0,f,X) should be an error
rather than a failure, I'll live with it quite happily.

	The important thing is that failure to select a small
	clear set of principles governing the error-reporting
	behaviour of evaluable predicates is

			PROFESSIONAL

			INCOMPETENCE

	in a standards committee, to the extent that they are
	free to specify interfaces.

> When we consider built-in predicates as a shorthand for (possibly infinite)
> series of unit clauses, the main question to answer is how to handle
> type and range errors, if any, because the call
> 		arg(fred, p(X), A)
> just fails, although we want it to raise an exception. Consequently,
> we have to introduce types into the error concept, and what these types
> are can be subject of lengthy discussions.

Not at all.  In order to complain about something as a TYPE failure,
a system should provide the user with some direct way of testing or
ensuring that an argument belongs to that type, so that it can rely
on the type name being in the user's vocabulary.  Thus the "types"
which a Prolog system may talk about are precisely the names of the
unary type tests it provides.  'integer', 'float', 'atomic', 'atom',
and so on are types.  "even integer", "finite float", "atom that
looks like a VMS file name" and so on, are not, _unless_ the system
in question offers unary predicates that recognise those terms.

One way of telling when "type failure" is the appropriate error
concept for something disliked about argument K of predicate P/M
is that the set of terms which would NOT provoke that report of
passed as argument K of predicate P/M does not depend on any of
the other arguments of P/M.  Consider

	functor(T, F, N)

var/nonvar apart,
	T can be any term
	N can be any non-negative integer
	F can be any atomic constant if N = 0
	F can be any atom if N > 0
What are the possible type failures?
	T : none.  Any term will do.
	N : if we had a built in type test non_negative_integer/1,
	    the type would be 'non_negative_integer'.  We haven't,
	    so the type is 'integer'.
	F : the only set that covers both cases is 'atomic'; we
	    can't reject a number as a type failure because numbers
	    are sometimes acceptable.
So we have
	functor(T:true, F:atomic, N:integer)

The concept here is
	1. the query must "obviously" fail because an argument
	   does not belong to a certain set of terms
	2. the set it should belong to can be determined
	   without knowing the value of any other argument
	3. the set has a built-in unary predicate recognising it.
There _is_ room for taste here in deciding which sets are important
enough to warrant having type test predicates (although I claim that
Prolog "types" should obey the "Interval principle" described in PS/6).
That having been decided, there is no further room for taste in this
scheme:
	functor(T, -1, b)	IS a type failure (b is not an integer)
	functor(T, b, -1)	is NOT (b is atomic, -1 is an integer)

The point is not to draw this particular conclusion, but that ANYONE
can turn the crank 1-2-3 and come to the _same_ conclusion without
ever having considered functor/3 before, let alone these special cases.

That doesn't mean that arg(3, f(a,b), X) could not be reported as some
kind of error, only that since the acceptable set here ({1,2}) for
argument 1 depends on the value of argument 2, it can't be a TYPE failure.

But for heaven's sake, consider the analogy:

	nth(N, [H|T], X) :-
	    (	N > 1, M is N-1, nth(M, T, X)
	    ;   N=:=1, X = H
	    ).

Does nth(a, [1,2,3], X) report an error?  Yes.
Should arg(a, f(1,2,3), X)?  Yes.

Does nth(0, [a,b,c], X) report an error?  No.
Then why should arg(0, f(a,b,c), X)?

Does nth(4, [a,b,c], X) report an error?  No.
Then why should arg(4, f(a,b,c), X)?

Does nth(3, X, a) report an error?  No, it binds X = [_,_,3|_].
Why should arg(3, X, a)?  Well, in a coroutining system, it _shouldn't_.
If the system can't suspend goals, then it should report what I call
an instantiation FAULT 
	-- a FAULT is when the Prolog system goofed
	-- an ERROR is when your program goofed
	-- a FAILURE is when your program got a "no" answer to a
	   question which raises the suspicion that it _might_ have goofed
because arg(3, X, a) makes a great deal of sense if only the Prolog system
_could_ solve it.

> I still think it is not a good
> programming style to rely on a certain behaviour when outside of the
> arity range.

It is not good programming style to rely on UNDEFINED behaviour,
we're in agreement on that.  But arg(N,T,A) *means* "is it true that
A is the Nth argument of T", and "no, because N is 0" is just as
sensible an answer as "no, because the Nth argument of T is 'fred'
and A is 'jim'".

> Furthermore, I don't think that "I have used the feature X in my
> programs and therefore it should be in the standard" is a sufficient reason.

Neither do I, and I have never argued that way.
My argument was "THIS IS THE LOGICAL READING OF arg/3 AND THEREFORE IT
SHOULD NOT BE RULED OUT".  "I have used X" was evidence that the
behaviour in question is *useful*.  That's all.

One of the things a competent software engineer does is to try to ensure
that each of the important operations in a program can be described well
in a short sentence or phrase of natural language.  One of the things to
worry about when trying to design the operations, both for this reason &
in order that the operations shall form a useful algebra, is that as few
exceptions/special cases/rough edges as possible should exist.  For this
example, the sentence is

	arg(N, T, A)
	is true when N is an integer, T is a term, and
	A is the Nth argument of T.

What do we expect to be the negation of this; that is, when do we
expect such a query to fail?

	when N is not an integer, or T is not a term,
	or A is not the Nth argument of T.

It would make a great deal of sense for arg(fred, ..., ...) to fail
quietly, just as it does for arg(..., fred, ...).  The reason for
type FAILURES (as opposed to type ERRORS, which happen when a command
cannot achieve its side effect because some datum has the wrong type)
is to compensate for Prolog's inability to distinguish argument
positions any other way.  It's a practical tradeoff; part of the logical
definition of arg/3 is "screened off" because of the virtual certainty
that it reflects a mistake.  It _might_ make sense to require T to be a
compound term; PS/6 demanded a compound/1 type test.  I wouldn't
particularly like it, but it would be in accord with the principles I
have proposed.  If there were a non_negative_integer/1 then reporting
arg(-1, f(a), X) as a type failure would be in accord with my principles.
But rejecting arg(2, f(a), X) is not.  Not because I happen to have used
things like that, but because the falsehood of arg(2, f(a), X) follows
from its definition, and according to the simplest definition I can come
up with of what is a type failure, that is not a type failure.

-- 
The problem about real life is that moving one's knight to QB3
may always be replied to with a lob across the net.  --Alasdair Macintyre.