[comp.lang.prolog] Prolog Book Critique

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