ok@cs.mu.oz.au (Richard O'Keefe) (11/08/89)
WG17 against Sterling and Shapiro
--------- an evaluation ---------
I have complained, for several years now, that the BSI committee and
now WG17 do not seem to regard other people's work as important. I have
had in mind other people's programs,but that's not the only kind of work
that deserves to be protected. A particularly important class of work I
believe should be protected is Prolog textbooks, not that there are many
of *those* that I like. With respect to textbooks,there are three kinds
of people who will be hurt by a standard which differs too much from the
descriptions in the books:
(a) the publishers, whose current offerings will be diminished
in value
(b) the authors, who will either have to produce new editions
of their works, or see their books decline in the market as
no longer relevant (e.g. John Stobo's book, which was to
a large extent based on the standard as it stood when he
wrote, now no longer reflects *either* the standard *or*
an existing Prolog)
(c) the purchasers, who bought books which _did_ describe
Common Prolog, but who will have to purchase additional
books if they want something that describes the standard.
I wonder whether it would somehow be possible for the publishers (such
as Springer-Verlag, MIT Press, Prentice-Hall, and one or to others) to
have a representative on the ISO committee -- it seems to me that they
have done more for the Prolog community than the committee have, and
definitely deserve to have their interests represented. In this case
their interests (continued markets for their books) coincide with their
authors' interests and ours.
To demonstrate that there is a real threat to existing worthwhile
books, I thought it would be appropriate to look at Appendix B of
Sterling and Shapiro's "The Art of Prolog". It has to be admitted that
some of the predicates they describe there are specific to Wisdom Prolog.
In the table which follows, I've labelled such predicates "private",
others are labelled "common".
GRAMMAR RULES common DELETED
atom/1 common present (used to be broken, ok now)
integer/1 common present
atomic/1 common present
constant/1 private
functor/3 common present
arg/3 common modified
var/1 common present
nonvar/1 common present
Note: the change to arg/3 is that in arg(N,Term,Arg), if N < 1 or
N > arity(Term) an error MUST be signalled instead of the former
quiet failure. This contradicts explicit statements in S&S, and
more importantly it is incompatible with the explanation in S&S
that "The predicate <arg> is defined as if there were an infinite
table of facts." Here is an example of a predicate which would
be *broken* by this change:
% sub_term(Kernel, Term)
% is true when Kernel is a sub-term of Term.
sub_term(Term, Term).
sub_term(SubTerm, Term) :-
nonvar(Term),
functor(Term, _, N),
sub_term_scan(N, Term, SubTerm).
sub_term_scan(N, Term, SubTerm) :-
arg(N, Term, Arg),
sub_term(SubTerm, Arg).
sub_term_scan(N, Term, SubTerm) :-
N > 1,
M is N-1,
sub_term_scan(M, Term, SubTerm).
This code is not found in Sterling & Shapiro, but according to the
definition of arg/3 found on p138 it is perfectly legal. In WG17's
N40, it _MUST_ break. If N40's definition is adopted, you will
not be able to use Sterling & Shapiro as a guide.
assert/1 common DELETED (new incompatible assert/2)
asserta/1 common DELETED (new incompatible asserta/2)
assertz/1 common DELETED (new incompatible assertz/2)
retract/1 common DELETED (new incompatible retract/2)
abolish/2 common DELETED (new incompatible abolish/1)
Note: I personally strongly dislike abolish/2, as it is inconsistent
with other DEC-10 Prolog operations. The general convention is that
a meta-predicate which is free of side effects takes a single predicate
specification in the form of a term which looks like a call to that
predicate, e.g. current_predicate(_, foo(X,Y,Z)) is true if foo/3 is
a user-defined predicate, while a meta-predicate which changes the
state of the system in any way takes an argument which specifies a set
of predicates. So abolish/2 is NOT consistent with this convention,
and N40's abolish/1 -- which presents the same interface as Quintus's
abolish/1 *IS* consistent with this convention. This is just about
the only case where I like N40's version better than what is common.
Unfortunately, my likes and dislikes really don't matter; abolish/2
_is_ the Common Prolog operation and abolish/1 _isn't_. It is fair
enough to add abolish/1 and recommend that abolish/2 should no longer
be used, but it is _not_ fair enough to delete abolish/2. Worse still,
N40's particular version of abolish/1 cannot be used to implement
abolish/2:
abolish(F, N) :- abolish([F/N]).
won't work because the former abolishes static predicates and the latter
does not (in N40, that is; in Quintus Prolog it works just fine).
consult/1 common CHANGED
reconsult/1 common CHANGED
[...] common DELETED
Note: Quintus Prolog and some others have deleted consult/1 and
renamed reconsult/1 to consult/1. This one is debateable.
Note: that's not what N40 changed, though. N40 changed the
argument from a file name to a stream.
clause/2 common present
listing/0 common present
listing/1 common present
read/1 common present
Note: Sterling and Shapiro do not describe Prolog syntax in detail.
N40 DELETES character lists, which does break S&S, but that's one of
the few explicit differences.
sread/2 private provided as read_with_names/2
Note: this operation was present in the public-domain implementation
of DEC-10 Prolog syntax as read/2; it was also provided in some versions
of C-Prolog as read/2, and is in the Quintus library as portable_read/2.
A similar operation is in NU Prolog as readTerm/3. The name and
interface in N40 are quite sensible, and as this is one of the areas
where there was no agreement, I think read_with_names/2 is a Good Thing.
write/1 common present (vague)
writeq/1 common present (vague)
display/1 common present (vague)
displayq/1 private
print/1 common modified (probably unintentional)
Note: in Common Prolog, if you call print(f(X,Y)), and your definition
of portray/1 does not match f(X,Y), print/1 then checks to see whether
'f' is an infix operator. If it is, it will print(X), write f suitably,
and print(Y), possibly with parentheses around the whole. But N40
states clearly in 9.12.2.1 c) that the principal function symbol must
be printed first and then the arguments, and there is no exception for
infix or postfix operators. Thus if there is no portray rule which
matches A<B, the command
print(1 < 2)
will write out "1 < 2" in Common Prolog, but must apparently write
something like "<(1, 2)" in WG17 Prolog. As I say, this is probably
unintentional, but there is nothing whatsoever in 9.12.2 to say that
print/1 ever has anything to do with operators.
see/1 common DELETED
seeing/1 common DELETED
seen/0 common DELETED
tell/1 common DELETED
telling/1 common DELETED
told/0 common DELETED
Note: it _is_ possible to define these operations in WG17 Prolog,
but it is impossible to use close/1 with these files. N41 suggests
how see/1 might be coded, but the code is seriously incorrect,
which suggests that see/1 ought to be specified in the standard so
that other potential implementors will get it right...
flush/0 private
Note: some such operation is important, but it has a variety of
names. In DEC-10 Prolog it was ttyflush/0 only. In Quintus Prolog
it is ttyflush/0 and flush_output/1. Unfortunately, it is rather
OS-dependent. (UNIX programmers need to beware of the distinction
between fflush() and fsync().)
get/1 common DELETED
get0/1 common DELETED
skip/1 common DELETED
put/1 common DELETED
tab/1 common DELETED
nl/0 common DELETED
tty*/1 common DELETED
Note: I've summarised ttyget, ttyget0, ttyput, ttytab, ttyskip, ttynl
in one line because I don't think they are very important, but that's
just my own taste, and it would certainly be easy enough to specify
them in the standard. I regard the dropping of nl/0 as excessively
odd; it is important for portability reasons to keep the value of the
new-line character (or whether it is one character or two or what)
out of code.
op/3 common present (modified)
Note: in Common Prolog the third argument of op/3 can be a list of
atoms, in N40 (and clarified in N41) this must be reported as an
error. Also in N40 you are not allowed to have an operator which is
both infix and postfix.
save/1 common DELETED
Note: this is admittedly OS-dependent, and with shared libraries,
memory areas mapped to files, and so on, it is increasibly hard to
implement, let alone define. Something could perhaps be done along
the lines of Quintus' save_program/1, but on the whole it is reasonable
to leave this out of the present standard.
log/0 private
Note: actually, it does exist in DEC-10 Prolog, but the operation has
been dropped from some other Prologs. Debateable.
true/0 common present
fail/0 common present
! common present
exit/0 private
Note: the Common Prolog operation is not exit/0 but halt/0, and that
_is_ included in N40. So is a new halt/1 operation.
abort/0 common DELETED
call/1 common present
not/1 commonish RENAMED
Note: the only negation operation in DEC-10 Prolog is the
"unprovable" predicate (\+)/1 (think of a crossed-out |-).
not/1 is a DEC-10 library predicate which reports an error
if its argument is not sufficiently instantiated to be sound.
Many Common Prologs identify not/1 and (\+)/1, which is a
pity. WG17 Prolog does not use either name, but introduces
a new name: fail_if.
name/2 common DELETED
Note: name/2 can be used to convert between numbers and character
lists, an operation which Quintus Prolog calls number_chars/2. It
has to be admitted that name/2 was rather muddled in DEC-10 Prolog,
and that it _ought_ to be supplemented by new atom_chars/2 and
number_chars/2 predicates, or something like that, but it should
not be deleted, and the new scheme should not provide _less_ power!
To convert between numbers and character lists in WG17 Prolog, the
following definitions appear to be necessary:
number_chars(Number, Chars) :-
( number(Number),
!,
swritef(String, q, Number),
string_chars(String, Chars)
; var(Number),
!,
string_chars(String, Chars),
strlength(String, W),
( substring(String, _, _, "."), !,
sreadf(String, f(W), Number, "")
; substring(String, _, _, "E"), !,
sreadf(String, f(W), Number, "")
; substring(String, _, _, "e"), !,
sreadf(String, f(W), Number, "")
; sreadf(String, i(W), Number, "")
)
; Number is Number+0
).
string_chars(String, Chars) :-
( string(String),
!,
string_list(String, UnitStrings),
units_chars(UnitStrings, Chars)
; var(String),
!,
units_chars(UnitStrings, Chars),
string_list(String, UnitStrings)
; concat(String, String, _)
).
units_chars([], []).
units_chars([UnitString|UnitStrings], [Char|Chars]) :-
char_int(UnitString, Char),
units_chars(UnitStrings, Chars).
(No, I am _not_ sure that this is correct. But it is the best I can
come up with.)
repeat/0 common present
, /2 common present
; /2 common present
=.. /2 common present
= /2 common present
\= /2 commonish present
Note: (\=)/2 is not built into DEC-10 Prolog or Quintus Prolog, but
is a library predicate in both. It is built into many other Common
Prologs.
== /2 common present
\== /2 common present
system/1 private
Note: some such operation is provided in most Prologs these days.
In Quintus Prolog it is unix(system(X)) or vms(dcl(X)) or
cms(system(X)) or whatever, and there is a library predicate
system/1. In NU Prolog it is system/1. In LPA Prolog on the PC
it is dos/1. system/1 is a good name for it, and it might as well
be in the standard.
systemp/2 private
Note: in NU Prolog this is systemPredicate/1 and systemPredicate/2.
In Quintus Prolog it is predicate_property(Goal, built_in). In LPA
MacProlog, it is sdef/1. It's clear that you want access to this
information, but there has been no common way of doing it in the past,
and it would be better to have a single predicate like the Quintus
one than half a dozen separate ones.
save_term/1 private
unsave_term/1 private
Note: these commands are not listed in the index, and I don't think
the text uses them. The text does describe set_of/3 and bag_of/3
(which are subtly different from setof/3 and bagof/3; the change is
not an improvement but introduces a bug). Program 17.3 is a version
of findall/3, but it is rather badly broken. For example, if you
call find_all_dl(., fail, X) it should return a representation of
the empty list, but instead it fails, leaving $instance($mark) in
the data base.
The fact that both Clocksin&Mellish and Sterling&Shapiro managed to
produce buggy versions of findall/3 suggests that findall/3 should
be in the standard and should be spelled out with the utmost clarity.
It _is_ in the standard, except that WG17 have changed the name from
findall/3 to bag/3. (There is no set/3, in case you were wondering.)
As to whether bag/3 is described "with the utmost clarity", I do not
find it so, but you ought to get a copy of the draft and judge for
yourselves. (It _is_ described formally, as indeed it should be,
it's just that I don't understand the formal description.)
member/2 commonish
append/3 commonish
Note: these two operations can easily be defined in Prolog, and in
many Common Prologs they are library predicates, not built in. The
usual code for them will work fine in WG17 Prolog.
iterate/1 private
This can be coded in Common Prolog as
iterate(Goal) :- call( (repeat, \+ Goal) -> true ).
It can be coded in WG17 Prolog as
iterate(Goal) :- call( (repeat, fail_if(Goal), !) ).
or as
iterate(Goal) :- once( (repeat, fail_if(Goal)) ).
I can't think of a good use for it, but it's definable, so no harm done.
fork_exec/2 private
Note: this is not listed in the index of S&S and the text does not
appear to describe it. It sounds OS-dependent, whatever it is.
ancestor/2 private
cutg/1 private
retry/1 private
Note: these operations are apparently used in the WISDOM Prolog
debugger. There is a Common Prolog predicate ancestors/1 in terms
of which ancestor/2 is definable. cutg/1 is a form of ancestral cut.
Debugging is outside the scope of the standard, and other Prologs
use different support predicates.
< /2 common modified
:= /2 private
is /2 common modified
Note: := /2 is a private alias for is/2. I dislike it on the grounds
that it doesn't behave like assignment, so why make it look like
assignment? WG17 Prolog permits the right hand side of is/2 to be a
string, so you can say X is $foo$. Earlier drafts had several string
functions which have been replaced by cleaner and better predicates
(still not as good as the ones in Xerox Quintus Prolog...), so this
may just be a relic of the past. Arithmetic comparisons may have
strings in them, but must then always fail, again this appears to be
a relic of the past. Apart from strings, arithmetic expressions in
WG17 Prolog are mostly an extension of expressions in Sterling & Shapiro.
--------
------
----
--
Summary.
If Sterling and Shapiro want to make their book compatible with WG17
Prolog as it currently stands, they will have to make changes to every
chapter. My estimate is that roughly a quarter of the text will need
to be rewritten, though all of it will have to be checked carefully.
The amount of labour involved is roughly comparable to the amount of
labour required to convert an elementary Pascal textbook to an
elementary Modula-2 textbook.