thom@sbcs.sunysb.edu (Thom Fruhwirth) (07/25/89)
Some remarks on "Prolog User's Handbook - a library of utility programs" by Bogdan Filipic, Ellis Horwood Series in Computers and their Applications, 1988 Despite the main title this book does not introduce Prolog, it is a collection of utility programs for list processing, arithmetic operations, screen managment and procedural constructs. While "Prolog by Example" (Springer) is full of typos and programming errors (but introduces more elaborate examples like a toy lisp interpreter), all predicates in this book work - somehow - and there are few typos. My complaint about this book is that it is inconsistent. Some predicates are defined on a very high level of abstraction, in a specification like style (p.17,p.20,p.25,p.53) (they may be extremely inefficient), most others are implemented in a procedural, cut loaded way. Sometimes both approaches are mixed into the same predicate (see below). As in most Prolog programs I have seen, cut is overused. I also do not like the procedural constructs (like for-loops) introduced into Prolog, but that is a personal statement, as I prefer the declarative side of Prolog. Each predicate is given a page, stating its name, description, mode, definition, remarks, examples and references to other predicates. I would like to see every Prolog program documented in this way, although I miss information about types and backtracking behaviour/determinacy of the predicates. With types I mean primtive types (atom, integer, float, compound, lists). It is important two know about them, especially when built-in predicates are involved. Depending on the system, they may either silently fail or produce a run-time type error when called with wrong types. In the book, some system-predicates are protected against ill-typed calls by type-checking their arguments before (e.g. integer(X), integer(Y), X<Y) (p.70,p.71,p.77), others are not (p.69,p.72-p.76) - thats what I also mean by inconsistency. Examples for some predicates do not reflect their meaning, they are incomplete. (For most predicates, half of the page is left blank, so there is enough space to add types, examples and backtracking information). A solution to the above mentioned different implementation approaches (specification versus optimized implementation) might be to give both. --------------- Let me give some detailed examples for the above complaint about inconsistency. Some well-known predicate names like append/3 (p.15), merge (p.33), insert (p.23) are used for different purposes, which is confusing. age/2 (p.64) is the name of a predicate for "alphabetic greater". Tail end recursion seems to be avoided, which not only makes the predicates more inefficient, but also results in unexpected behaviour (p.19, delete_ duplicates/2, "..goes from the last element of a list to the beginning.."). In most cases subgoals need just to be reordered (p.21,p.29, p.57), sometimes (p.19) applied to different arguments, some would need an additional accumulator variable (length/2 p.28, p.35, p.38). If tail-end recursion is present, a useless cut is present just before the recursion (p.85,p.86,p.89). The procedural view of Prolog is emphasized by introducing predicates like delete_n/3 (p.20) and insert_n/4 (p.25) which have the same body (up to renaming of variables) but slightly different heads. Some predicates have redundant clauses (which consequently need cuts) (p.29,p.33,p.56,p.59,p.61), or do not work with empty lists for no good reason (another way to introduce cuts) (p.31, p.41).A redundant subgoals is on p.40. Some predicates are made deterministic with cuts (p.43) for no good reasons, some are indeterministic for no good reason (p.44), or do not work as expected because of cuts (p.45, p.54,p.69...). Facts with cuts in the body are found everywhere in the chapter about screen managment. As most Prologs index on the first argument, those cuts are unnecessary. If there is really a need for it, they should be placed after a call to such a predicate of facts, not in the body of each fact. Some predicates backtrack unnecessarely producing the same result again due to overlapping clauses (p.64,p.66). I found typos (some of which result in erroneous predicates) on p.35, p.88, p.41, p.44, p.122. The predicate process_list/2 (p.37) which maps to elements of a list using a binary predicate resulting in a list can only be used once in a program, because it uses information asserted (!) into the database. Assert instead of an argument is also used in (p.85). The predicate find-all/3 (p.110) has the usual flaws, (see some old comments about findall/3 by Richard O'Keefe. On p.119, a subgoal call((Q,!)) is used. Do you immediatly know why not call(Q),! is used? With an additional predicate like once/1 this could have been made much clearer. Regarding the mixture between specification and procedural style, take a look at the following predicates from the book (p.69,p.70,p.74): fac(0,1). fac(N,F):- integer(N), N>0, N1 is N-1, fac(N1,F1), F is N*F1. is more like a specification as: max(X,Y,X):- X>=Y,!. max(X,Y,Y). Note this definition is extremely dangerous, as it erroneously succeeds with calls like (?- max(2,1,1)) due to the cut. abs(X,X):- X>=0,!. abs(X,Abs):- X<0, Abs is -X. This is a mixture of procedural and declarative style. Either abs(X,Abs):- X>=0,!, X=Abs. abs(X,Abs):- Abs is -X. abs(X,X):- X>=0. abs(X,Abs):- X<0, Abs is -X. would have done it. On p.112 the author states, that the predicate numbervars/3 is "rarely used" (why did he include it then?) and used to produce more readable output. As numbervars/3 is also used in meta-programming to reify variables, this might indicate that the author is not familiar with higher-order predicates and meta-programming. ---------------- Concluding, I cannot recommend the book. Thom Fruehwirth SUNY at Stony Brook Computer Science Dept. NY 11794-4400