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.