Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!samsung!munnari.oz.au!goanna!ok From: ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) Newsgroups: comp.lang.prolog Subject: Re: Standards question: behavior of arg/3 Keywords: ANSI, standard Message-ID: <3992@goanna.cs.rmit.oz.au> Date: 17 Oct 90 05:55:31 GMT References: <9888@bunny.GTE.COM> Organization: Comp Sci, RMIT, Melbourne, Australia Lines: 209 In article <9888@bunny.GTE.COM>, bs30@GTE.COM (Bernard Silver) writes: > The ISO draft specifies that arg(N,Term,Arg) (with N and Term > instantiated) reports a range error if N is greater than the arity of > Term. In contrast, many existing Prologs just quietly fail in > this situation. Which do you prefer? In particular, do your programs > ever rely on the quiet failure to end a recursion? I am sick of this kind of thing. Back in 1984 I proposed a set of "design principles", a *small* set of *very simple* rules, which would tell what the answer *had* to be. It is quite simply a BLUNDER to consider each predicate separately. The principle here was that something which could in principle be defined by a (possibly infinite) table should *act* just like that table. An error report would be *required* from a Prolog processor if it was unable to simulate the behaviour of the table exactly for some particular goal, and type errors would be permitted, but that's all. What does that tell us about arg/3? Well, could arg/3 be defined by a table? Yes: arg(1, ''(X), X). arg(1, ''(X,_), X). ... arg(1, '\1'(X), X). arg(1, '\1'(X,_), X). ... and so on. For each function symbol , for each positive arity , and for each integer 1 <= <= there is a fact arg(, (X<1>,...,X,...,X), X). This is an infinite table. For any query to arg/3, the table defines a set of possible answers. In cases where this set of answers is not finite, it makes sense for the Prolog processor either to delay the goal (as NU Prolog can) or to report an instantiation_fault (as Quintus Prolog can). When do we have this unbounded nondeterminism? When the Term argument is a variable. As long as the Term argument is not a variable, the solution set is finite. So the query ?- arg(N, 27, X). makes perfect sense and has no solutions, while the query ?- arg(N, [a,b,c], X). makes perfect sense and has two solutions N = 1, X = a ; N = 2, X = [b,c] . THIS IS A CHANGE FROM DEC-10 PROLOG. It is a change that shouldn't break any sensible program, and is easy to repair: a program which relied on ?- arg(N, T, X) failing when var(N) can be repaired by adding nonvar(N) or integer(N) in front of it. Remember that the model is "pure predicates should act exactly as if defined by a possibly infinite table, except that a processor must report an error if it is unable to simulate the table exactly, and that it may report type failures." What counts as a type? Only things that have a standard recognition predicate. So integer and float and atom and so on count as 'types' by virtue of the existence of the predicates integer/1, float/1, atom/1, and so on. A range like "1..13" does not count as a type. Why allow type failures to be reported? To help people find places in their code where they have accidentally switched two arguments. The first argument of arg/3 will almost always be an integer. The second argument of arg/3 will almost always be a compound term. Reporting a type failure if the first argument is not an integer will help people who think of the "subscript" following the "array". So a type failure report is sufficiently useful to over-ride the normal preference for exact simulation of the table. What, however, is the advantage of whining about values of N that like outside the range? Very little, I should think. In particular, I have frequently used p(N, T, ~stuff~) :- arg(N, T, A), !, % now we know that N >= 1 ... p(0, T, ~stuff!) :- ... This seems to me to be a perfectly sensible use of arg/3. Further, suppose I want to test whether a term has 3 or more arguments. (It might be useful to do this in your own version of write/1, because that means that the function symbol won't be written as an operator.) If arg/3 is implemented sensibly, you have only to ask arg(3, Term, _) % if this succeeds, arity{Term} >= 3 It is easy to see what kind of misconception about arg/3 could lead to the first argument being a non-integer (argument order mistake). I can't think of a likely misconception that would result in the first argument being outside the range 1..arity{Term}, and I *can* think of lots of reasons why that has been useful. The most important thing, as far as I am concerned, is that as a programmer I should *NOT* have to remember a separate ad hoc set of special whimsical rules about each built in predicate. There should be a *short* list of principles that I can memorize that cover the *whole* collection of standard predicates. So whatever is decided about arg/3 should be made into a principle and *uniformly* applied to all relevant predicates. Here I have to say that the principle "Non calcitrandus in dentes artifex" (the workman is not to be kicked in the teeth) has veto authority. But the principle I am recommending here is something *enabling*, it is a requirement that built-in predicates should be as *general* as they can be. Most Prologs won't solve for the first argument of arg/3 (in Quintus Prolog the predicate that does _that_ is called genarg/3). But allowing this doesn't break anything irreparably; I showed above how to stop that if you want. > Some Prologs ``extend'' arg/3 so that arg(0,Term,Arg) (attempts to) > unifies Arg with the functor of Term. (Quintus provides a library > predicate to support this use). This may be misleading. The Quintus library provides a predicate arg0/3 (and also a genarg0/3) *not* because anyone at Quintus ever thought it was a sensible thing to do, but in order to help customers who had written code for other Prologs which used such a kluge. I never intended it to be more than a piece of scaffolding that a customer could discard once his code was corrected. > Do people use this feature? The ISO > standard specifies that this use should raise an error, many existing > Prologs either fail quietly or behave as above. People manifestly *do* use this feature in Prologs that have it. In part this is because of the incredible sloppiness of some Prolog manuals, whose authors either never knew the difference between a function symbol (an atom) and a functor (an atom/arity pair) or didn't think their readers deserved precision. There is also another reason. I think everyone who has written a theorem prover in Lisp has realised at some time that you can treat the function symbol of a term as if it were an argument of that term. If you get that far, you get a passing grade in Lisp hacking. But in order to pass your logic course, you have to take the next step, which is to realise that the function symbol *isn't* an argument of the term. Think about this one fact: you can use arg/3 to find out what any argument of a term is OR to fill in an argument, but you can't "fill in" the function symbol of a compound term because you had to supply the function symbol and arity together in the first place in order to _have_ a term. Using arg/3 to get at the function symbol is unnecessary, because functor/3 already exists, and it's inconsistent, because in the model of what terms _are_ in ISO Prolog you can't "fill in" the function symbol. I repeat, the "non calcitrandus" principle has veto power over every preference. Where something is already portable, it should not be broken. But in this case, code that used arg(0, Term, X) to mean what functor(Term, X, _) means was NOT portable, so making the standard sensible won't make those programs any less portable than they were before. *IF* the ISO standard were to rule that arg/3 acts like the infinite table we use to explain it, then it would be important to ensure that every value of N accepted as input could be generated as output and every value of N generated as output would be accepted as input, and a program which calls ?- arg(N, f(1,2), Z) is entitled to be told by the standard how many solutions it is going to get. *HOWEVER* if the ISO standard were to rule that it is an error for N to be uninstantiated, then it would be perfectly acceptable for the standard to leave the behaviour of arg(N, T, A) *undefined* for integral N outside the range 1..arity{T}. In that case *both* Prolog systems which are DEC-10 compatible *and* Prolog systems with the pointless kluge would conform to the standard, vendors would not have to change that part of their implementation, and the portablity of code using the kluge would remain exactly what it is now. > If you believe in quiet failure, when should arg/3 raise an error? You shouldn't be asking questions about arg/3. You should be asking "what rule do you recommend for ALL built-in predicates?" In this case, *precisely* the same question about the domain of N applies to the built-in predicate functor(Term, Symbol, Arity). We should have ONE answer. > Is arg(-3,Term,Arg) ok? Except for the omitted spaces (it should be arg(-3, Term, Arg)), of course it is. > What about arg(a,Term,Arg)? Unlike arg(-3, Term, Arg), *this* one could arise from the misconception that the argument order is arg(Term, Index, Arg), so it would make sense to *allow* an implementation to report a type failure. I would point out that if you think of arg/3 as defined by that infinite table, the query is sensible and false, so it also makes sense to *allow* an implementation to deliver the defined result, namely 'false'. I really can't stress enough that it is foolish to consider each predicate separately. As a programmer I shouldn't have to remember half a dozen facts about errors PER BUILT-IN; I should just have half a dozen facts about errors *for the whole standard* to remember. This was obvious 6 years ago! I mean, this is an *elementary* principle of programming language design! -- Fear most of all to be in error. -- Kierkegaard, quoting Socrates.