[comp.lang.prolog] PROLOG Digest V5 #78

PROLOG-REQUEST@SUSHI.STANFORD.EDU (Chuck Restivo, The Moderator) (10/25/87)

PROLOG Digest            Tuesday, 27 Oct 1987      Volume 5 : Issue 78

Today's Topics:
                     Query - Negation as Failure,
                  Implementation - Prolog Machines,
                     LP Library - Rodger's Review
----------------------------------------------------------------------

Date: 22 Oct 87 08:43:54 GMT
From: reeves@locus.ucla.edu
Subject: forall/2 and negation as failure

I'm having a problem reading forall/2 and wondered if anyone  could  help.
In    Sterling    and    Shapiro   p.   270-271,   the   claim   is   that
forall(Goal,Condition) is true for all solutions  of  Goal,  Condition  is
true.  Under this reading not(Goal, not(Condition)) works fine as far as I
can tell.

They claim that

    forall(father(X,C),male(C))

is checking _for_ fathers that have all male children.  My reading is that
it  is  checking  _that_  all  fathers  have  all  male  children.   Their
implementation returns two solutions for the query: the fathers that  have
only  male  children.  In  the database, however, there are fathers who do
not have only male children, so (I would think) the query should fail.

Is there a reading for forall/2 for the following implementation (from S&S)?

        forall(G,C) :- setof(C,G,Cases), check(Cases).

        check([Case|Cases]) :- Case, check(Cases).
        check([]).

Alternatively, is there a case where:

        forall(G,C) :- not(G, not C).

behaves counterintuitively, due to the use of not as negation-as-failure?

-- John Reeves

------------------------------

Date: 23 Oct 87 07:13:08 GMT
From: mcvax!unido!ecrcvax!jclaude@uunet.uu.net  (Jean Claude Syre)
Subject: Prolog Machines

PROLOG MACHINES:

Here we go: below is a (non-exhaustive) list of machines/prototypes/
developments/basic research under way in different places around the
world, extracted from my presentation at a Panel on Parallel  Inference
Machines at IJCAI87, Milano.

At ECRC, the Computer Architecture Group (17 people) is working
quite a lot on Sequential machines, but is also implementing a
Parallel Logic Programming System called PEPSys, on a commercial
multiprocessor. For more info contact us at ECRC Arabellastr.  17
D-8000 Munich 81 , BRD.  We also  would be pleased to get more info
on your activities (papers, reports, performance studies, ...) if
they deal with these topics, THANKS.


WORD is Word Length, KLIPS means the peak performance on Concat or
Naive reverse. The Klips is the most fuzzy unit to measure
performance of Prolog Systems (between 1 and 2000 assembly
instruction of a CISC computer, imagine..., but my opinion is that
REAL performance can be estimated at half of the peak performance for
Hardware implementations of Prolog, wheras it could be around 1/6th
to 1/10th of the peak performance for Software implementations).

NAME     WORD   KLIPS   BY              REMARKS

PSI I    40      25     Icot            Inter., stand alone

PEK      34      40     U. KOBE

PSI I    40     110     Icot            Compiled WAM

PLM      32     400     U. Berkeley     WAM

CHI      36     250     NEC C&C         ECL, WAM

CHI II    ?     500?    NEC             ? (info wanted!)

IPP      32    1000     Hitachi         ECL, WAM

PSI II   40     250     Icot            stand alone wks,WAM

X1       32     300     Xenologic       PLM, SUN co-proc.

ICM3     32     530     ECRC            WAM, co-proc.

ICM4     32     680?    ECRC            RISC, fast memory

KCM      64     650     ECRC            WAM, attached proc

DLM      38     600?    BAe             exp. board

AI32     40     200?    Hitachi         co-proc. chip

Pegasus  40     240     Mitsubishi      RISC chip

SM45000  40      ???    Inference       AI co-proc

MDS      ??     200??   Matra           chip set

TOSHIBA ?  ?    650                     Attached Processor


OTHERS? CERTAINLY. I APOLOGIZE

Current work at ECRC:

KCM: Knowledge Crunching Machine (with Siemens, BULL, ICL): 64-bit,
tagged architecture. Private memory, two large Data and Code caches
Full Prolog/Lisp system, with extras.

------------------------------

Date: Sun, 18 Oct 87 22:05:27 PDT
From: quintus!ok@Sun.COM (Richard A. O'Keefe)
Subject: Rogers' textbook.

    Having suggested in my previous message to this Digest that it
would be a good idea to discuss textbooks, here's some provocation.
I'm going to comment on a textbook:
        @Book(RogersPrimer
        ,  Key = Rogers
        ,  Author = "Rogers, Jean B."
        ,  Title = "A Prolog Primer"
        ,  Publisher = Addison-Wesley
        ,  Year = 1986
        )

    It is essential to point out before I start that Rogers explicitly
says "The audience for whom this book was written includes mature
learners who are neither experienced in computer programming nor
particularly sophisticated in mathematics."  Rogers does claim that
"This book is also an effective introduction to Prolog for people
already quite familiar with computer programming", but obviously the
requirements of the first audience are the main control.  I am quite
unable to judge the suitability of this or any other textbook for that
audience.  The book "has been used for instruction in courses with
computer science and other graduate students, computer science under-
graduates, and undergraduates from non-computer-science majors.  Their
feedback was a guiding factor in revisions for the book".  It is worth
noting that Clocksin & Mellish can make precisely the same statement.

    I should explain that I have not chosen to comment on Rogers' book
as an example of a notably bad book.  Several people here liked it well
enough to buy copies.  I am not commenting on the book as a whole in
any way at all.  There are just two specific points I want to raise.
You should not attempt to guess my opinion of other aspects of the
book.  For the most part, I have none.

    The two points I want to discuss are setof/bagof and data base
design.

SETOF/BAGOF

    Rogers explicitly states that "one version of Prolog is used
consistently in the examples and the explanations.  That version is
Standard DECsystem-10/20 Prolog".  We are entitled, therefore, to
conclude that when the book describes setof and bagof,
    (a) Rogers has read the DEC-10 Prolog manual
    (b) Rogers' book is intended to be true about DEC-10 Prolog

    WHAT THE DEC-10 PROLOG MANUAL SAYS

    setof(X, P, S)
        Read this as "S is the set of all instances of X such that P is
        provable, where that set is non-empty".  The term P specifies a
        goal or goals as in call(P).  S is a set of terms represented
        as a list of those terms, without duplicates, in the standard
        order for terms.  If there are no instances of X such that P is
        satisfied, then the predicate fails.

        The variables in appearing in the term X should not appear
        anywhere else in the clause except within the term P.
        Obviously, the set to be enumerated should be finite, and
        should be enumerable by Prolog in finite time.  It is possible
        for the provable instances to contain variables, but in this
        case the list S will only provide an imperfect representation
        of what is in reality an infinite set.

        If there are uninstantiated variables in P which do not also
        appear in X, then a call to this evaluable predicate may
        backtrack, generating alternative values for S corresponding to
        different instantiations of the free variables of P.  (It is to
        cater for such usage that the set S is constrained to be
        non-empty.)  For example, the call:
            | ?- setof(X, X likes Y, S).
        might produce two alternative solutions via backtracking:
            Y = beer,  S = [dick,harry,tom] ;
            Y = cider, S = [bill, jan, tom]
        -- This was written in English, where "cider" denotes an
        -- alcoholic beverage made from fermented apple juice.  RAO'K.
        The call:
            | ?- setof((Y,S), setof(X, X likes Y, S), SS).
        would then produce:
            SS = [(beer,[dick,harry,tom]),(cider,[bill,jan,tom])]
        Variables occurring in P will not be treated as free if they
        are explicitly bound within P by an existential quantifier.  An
        existential quantification is written:
            Y^Q
        meaning that "there exists a Y such that Q is true", where Y is
        some Prolog variable.  For example:
            | ?- setof(X, Y^(X likes Y), S).
        would produce the single result:
            S = [bill,dick,harry,jan,tom]
        in contrast to the earlier example.

    bagof(X,P,Bag)
        This is exactly the same as setof except that the list (or
        alternative lists) returned will not be ordered, and may
        contain duplicates.  The effect of this relaxation is to save
        considerable time and space in execution.

    X^P
        The interpreter recognises this as meaning "there exists an X
        such that P is true". and treats it as equivalent to call(P).
        The use of this explicit existential quantifier outside the
        setof and bagof constructs is superfluous, and therefore is not
        recognised by the compiler.
        -- But it still WORKS in compiled code.  RAO'K.

    WHAT ROGERS SAYS

    PP 134-135 (Using Built-in Predicates)

        Two predicates that gather instances of occurrences of objects
        are bagof and setof.  To use these predicates, we specify a
        goal and a variable in the goal.  The predicates search through
        the data base to find all appearances of the goal that can be
        proved true.  For each true goal, the constant value that was
        instantiated to the specified variable is gathered into the
        bag.  The bag (a list) is instantiated to the variable given in
        the third argument of the predicate.  If there are no
        occurrences, the goal fails.  This data base and query using
        the bagof predicate show how to collect a list of constants
        designated by the variable.
            parent(jan, bet).
            parent(jan, cat).
            parent(joe, ann).
            parent(joe, cat).
            ?- bagof(Child, parent(jan,Child), B).
            B = [bet,cat]
            yes

        If there is more than one possible list of instances because of
        another variable in the specified goal, the goal can backtrack
        and find other responses.  For example, given
            parent(jan, bet).
            parent(jan, cat).
            parent(joe, ann).
            parent(joe, cat).
            ?- bagof(Child, parent(Who,Child), B).
            Who = jan, B = [bet,cat] ;
            Who = joe, B = [ann,cat] ;
            no

        The instantiation of different bags can be gathered into a
        single response by using what is called an existential
        quantifier on the non-gathered variables from the goal.  The
        variable name followed by a caret (^) appears just before the
        predicate.  A predicate with several variables requires a
        sequence (e.g., A^B^C^D^).

        Using the data base above
            ?- bagof(Child, Who^(parent(Who,Child)), B).
            B = [bet,cat,ann,cat],
            Who = _45,
            Child = _24 ;
            no

        This existential quantifier (read X^ as "there is an X such
        that") is used only in bagof and setof.

        The setof predicate works as though bagof is called to gather
        the values into a list before the list is sorted.  As in the
        result of sort, any duplicates will have been removed.

        Using the data base above
            ?- setof(Child, P^(parent(P,Child)), S).
            S = [ann,bet,cat],
            P = _45,
            Child = _24 ;
            no

    P 203 (Appendix: Built-in Predicates)

        bagof(V,P,B)
            This predicate gathers all the instances of the variable V
            that appear in goals, P, that are provable.  It puts the
            instances into a list that is instantiated to the bag B.
            If there are no instances, the goal fails.

        setof(X,P,S)
            This predicate works as though bagof had been followed by
            sort.  That is, the elements in the list, S, are sorted and
            duplicates have been removed.  Existential quantifiers can
            be used with bagof and setof.  For example,
            bagof(X, A^B^C^one(A,B,C,X), B).

COMMENTS.

    It's been a while since I read the setof/bagof section in the
DEC-10 Prolog manual critically.  I am pleasantly surprised at how
precisely it manages to say just what is true.  It even manages to
convey the impression that setof/3 is the fundamental predicate and
bagof/3 is secondary, which is epistemologically true even if it may
not be true of an *implementation* of the operations.  Rogers, alas,
manages to convey the opposite impression.

    PROBLEM ONE:

        The really vital predicate for an implementor to provide and
        for a programmer to understand is setof/3.  setof/3 was added
        to the DEC-10 Prolog language to make certain types of data-
        base query expressible in Prolog.  bagof/3 is just a hack you
        can use when you know that the exact representation of the set
        doesn't matter too much.  Rogers manages to suggest that the
        way to understand setof/3 is to understand one way it might be
        implemented.

    A much worse problem is the foggy language.  Instantiation is a
process in which variables in a term TA are bound to other terms,
forming a new term TC.  TC is said to be an instance of TA, and TA is
said to be instantiated to TC.  <<Terms>> are <<instantiated>>,
<<variables>> are <<bound>>.  Since a variable is a special case of a
term, it is accurate to speak of instantiating a variable to a term.
But you cannot instantiate a constant to something, it hasn't got any
variables to bind.  If the variable X is bound to 47, it is true that X
is instantiated to 47, but it is not true that 47 is instantiated to X.
Rogers could perhaps employ the Humpty-Dumpty offence, but the nearest
thing to a definition of "instantiation" in the book is on p55, and as
that clearly talks about a <<variable>> being instantiated or
uninstantiated, the standard direction must be assumed.

    The same distinction applies in conventional languages, where an
assignment statement X := 47 is read as "47 is assigned to X" or "X is
assigned the value 47".  But X is not assigned to 47!  It's as
different as you paying tax to the IRS or them paying tax to you.
It is *especially* important to keep one's language straight when
teaching beginners, because they don't know enough to correct their
teacher, and a teacher with confused language is likely to have
confused students.

    I found Rogers' language in these two passages very confusing.  For
example, what is "an instance of an occurrence of an object"?  Neither
"object" nor "occurrence" is in the index.  Neither is "appearance of a
goal" defined that I can see.  The book does not say what it means for
a "list of constants" or anything else to be "designated by a
variable"; "designated" is not in the index.

    PROBLEM TWO:
        The language in these passages looks like technical language
        but is not being used with the precision proper to technical
        language.  A key technical word is being used backwards!  The
        effect is very confusing.

    You may dismiss this as "nit-picking".  Well, there are two answers
to that.  With all the earnestness I possess, I tell you that I found
Rogers' explanation of setof/3 and bagof/3 so confusing that if I had
been trying to learn Prolog from this book I would have given up trying
to understand setof/3 and bagof/3, and would have started to have
serious doubts about Prolog generally.  Without any earnestness at all,
I would point out that "nit" is one of the oldest words in the English
language.  It comes from the Indo-European stem "knid-" (so is at least
5,000 years old) and means the egg of a louse.  Picking nits out of the
hair is an essential part of health care, if there are any nits.  Could
any of you seriously suggest that heads should have lousy hair on them,
or lousy ideas in them?

    Let's suppose that one works one's way past the fog.  In a book for
beginners, it is difficult to tell the whole truth.  One's readers may
not possess the vocabulary needed to express the whole truth.  But
everything one says should be true.  It is quite proper to simplify,
but it is important to say that one has done so.

    PROBLEM THREE
        The following statements are reasonable inferences from
        what Rogers says, but are not true.
            In bagof(V, G, L) or setof(V, G, L)
        (a) V must be a variable.
        (b) V must occur in G.
        (c) L must be a variable.
        (d) G must bind V to constants.
        (e) G will be solved by checking the data base,
            not by general interpretation.

    Re point (e), I would remind you that Rogers explicitly says that
"The predicates SEARCH THROUGH THE DATA BASE TO FIND ALL APPEARANCES".
It is probably a coincidence that points (a), (b), (c), and (d) were
also exhibited by Lagache in his library documentation.  Since Rogers
must have had access to the DEC-10 Prolog manual, at least the
falsehood of (a) should have been apparent to her.

    Am I nit-picking again?  Not at all.  It is USEFUL that (a) is
false.  The DEC-10 Prolog manual even includes an example.  When
converting between one representation of a graph and another, I have
very often found it useful to do
        setof(Node-Neighbours,
            setof(Neighbour, adjacent(Node,Neighbour), Neighbours),
            Graph)
As for point (b), I have found it useful too, but at a minimum it is
useful to know that the Prolog system does not regard it as an error
for there to be no variable common to V and G, so that if that should
that be the cause of a bug in your program, you will know that Prolog
can't be expected to tell you about it.

    [   Begin digression.
        Having a call to setof/3 or bagof/3 where the first argument
        is ground or contains a variable not in the second argument
        is sufficiently odd that it is worth checking.  You might like
        to add this to your library:

        checked_setof(Template, Generator, Set) :-
                check_sb(Template, Generator, Set, set_of),
                setof(Template, Generator, Set).

        checked_bagof(Template, Generator, Bag) :-
                check_sb(Template, Generator, Bag, bag_of),
                bagof(Template, Generator, Bag).

        check_sb(T, G, _, _) :-
                \+ numbervars(T, 0, 0), % T not ground
                \+ ( numbervars(G, 0, N),
                     ( N = 0            % G not ground
                     ; numbervars(T, N, X),
                       X > N            % T not covered
                     )
                   ),
                !.
        check_sb(T, G, L, P) :-
                /* I don't care about efficiency here */
                Query =.. [P,T,G,L],
                format(user_error,
                    '~N! Bad template or generator~n! Goal: ~q~n',
                    [Query]),
                format(user_error,
                    'Enter true, fail, or abort. ', []),
                read(user_input, Command),
                call(Command).

        This code has been tested in Quintus Prolog.
        If you think this kind of mistake is one you are likely to make,
        it would be worth your while using an interface like this.  The
        cost of checking is comparable to the cost of finding one more
        solution.

        End digression.
    ]

    Concerning point (d), the same example in the DEC-10 Prolog manual
that refutes point (a) refutes point (d).  It is useful to be able to
collect proof trees, or picture descriptions, or fragments of code, or
whatever.  If p(X) is a query which instantiates X to constants, then
X^(p(X), name(X,Chars)) is a query which unifies Chars with a list of
character codes, and it is reasonable to write
        setof(Chars, X^(p(X), name(X,Chars)), SetOfNames)

    [   Begin digression.
        In fact it is more efficient not to do that.  The trouble is
        that executing an all-solutions predicate like findall/3, or
        bagof/3, or setof/3, involves copying all the instances you
        want, so the smaller the instances the cheaper.  It is also
        cheaper to compare smaller terms.  So a more efficient way of
        writing the query above is
                setof(X, p(X), SetOfConstants),
                maplist(name, SetOfConstants, SetOfNames)
        The original version of the query is a perfectly sensible thing
        to write, and the time to worry about efficiency like this is
        AFTER that part of the program is working.  Even when parts of
        the result can be recomputed, as here, it may be more efficient
        to copy the results rather than recompute them.  It all depends
        on the problem.  Feel free to make the first argument of an all-
        solutions predicate precisely as complex as suits the problem,
        but try not to make it any bigger.

        There is a general point here about when you should move a
        computation inside setof/3 and when it should be outside.
        Let's leave that for another time;  it belongs in a general
        discussion of sequence mapping.  But if the computation is
        likely to fail, or to produce smaller results, putting it
        inside may be a good idea.  Watch out for free variables!
        End of digression.
    ]

    There is a lot more I could say.  But that's probably enough.  It
may seem unfair to you that I have said so much about so very little of
Rogers' book.  After all, this is a primer.  Well, I repeat that I am
not giving a general review of Rogers' book.  (This is fun, but it is
time-consuming.)  Other parts of it may be excellent.  All I am saying
is that
        The sections of Rogers' book which describe setof/3 and bagof/3
        are confusing to read and may lead reasonable readers to believe
        things about those predicates which are not true.
It may, for all I know, be an excellent book in other respects.  I
repeat that I do not regard myself as competent to judge what is a good
book for complete beginners and what is not.

    If any of you is considering writing a Prolog textbook, I would
urge you to give setof/3, bagof/3, and findall/3 a chapter to
themselves, with lots of examples, chosen so that the all-solutions
predicates are naturally wanted to solve the problems, not contrived
"X likes Y" or "parent(X,Y)" examples invented to illustrate the
solutions.

    That's finished with setof/3 and bagof/3.  But there is another
point in Rogers' book which is worth discussing.  I'm not referring to
page 174, which is likely to leave the reader believing that
"module"="predicate".  If I were going to refer to that, I'd have to
refer to exercise 10.3.2, and I can't see any *point* in rewriting that
clause at all, let alone into three modules.  (Whatever they are.
"Module" is not in the index.  Looking at the answer, it appears that
"module"="clause".  Not in Quintus Prolog, Prolog-X, or NIP it isn't.]

    The second point concerns data base design, which is not something
that Rogers claims to cover, so this should not be read as a criticism
of things Rogers does claim to cover.  The example actually comes from
the chapter on arithmetic.

    Rogers says
        Suppose we have a Prolog data base that contains information
        about the presidents of the United States in the form:

            president(<name>, <birthyear>,
                <year began office>, <year left office>).

        From this base, we can derive several kinds of information.

        How long was a president in office:

            length_of_office(Name, Years) :-
                president(Name, _, In, Out),
                Years is Out-In.

        How old was a president when he took office:

            age(Name, Years) :-
                president(Name, Birth, In, _),
                Years is In-Birth.

And so on.  What is wrong with this?

    There is a problem endemic among all sorts of textbooks.  I first
had it brought to my attention in Statistics textbooks.  The problem is
this:
    -   authors write examples to illustrate methods.
        So they work from a known method to an invented example.
    -   students are trying to acquire two kinds of knowledge:
        -   how the methods work
            The examples are usually adequate for this
        -   WHEN TO USE a particular method
            And it is not uncommon for the methods to be
            inappropriate to the invented problems.
That is, very often there is a better way to handle a given example
than the method the example has been chosen to illustrate, so a student
who is trying to work out when to use the method is left confused.
I wouldn't be surprised to find the same problem showing up in
carpentry textbooks.

    The specific problem here is that Rogers is illustrating
arithmetic, and the examples she has made up do in fact illustrate the
aspects of arithmetic she is trying to explain.  BUT any interested
student is going to look at section 9.4 as an example of how to put
together a data base about American presidents AS WELL.  Indeed, as we
have seen with quicksort, it is not uncommon for students to miss the
real point of an example entirely and focus exclusively on the example
as a way of solving the invented problem.

    What's wrong with this design of the data base?  Briefly, the
information should be held in two predicates, not one:

        birth_year(Name, Year)
        presidency(Name, BeginningYear, EndingYear)

Indeed, there is a strong argument for splitting the second of these
predicates into two predicates itself:  for how are we to say anything
about the *present* president?

    I am not a citizen of the United States.  I have heard that a
president can serve for only two consecutive terms, but I do not know
whether a president can serve two separated terms, or more than two
separated terms.  I know that originally when a president died in
office the vice-president assumed his duties but not his title, but that
now he assumes the title as well, but I don't know whether that affects
the permitted number or consecutivity of terms.  I can see no a priori
reason why a president might not resign part way through one term and
then return to office 10 years later.  There is certainly nothing to
prevent Prime Ministers in many countries from holding office in
separated terms.

    So in principle, it looks as though there could be two clauses
for some presidents.  Let's imagine a hypothetical case:
        president(ryan, 1660, 1700, 1704).
        president(ryan, 1660, 1708, 1712).
Now let's pretend that we discover that we got the year of birth wrong
(not an implausible assumption), and want to change it.  Now we have to
change TWO places.

****************
*   A general rule of thumb for data base design is that each piece
* of information should be represented in ONE place.  The uniqueness
* of this place should not depend on the contents of the data base
* but should be built into the structure.
****************

    Now let's consider it from another angle.  Suppose we have
        president(macfarland, 1680, 1712, 1716)
and someone tells us no, it was 1716-1720.  We have to remember to copy
the BirthYear from the old president/4 fact to the new one.

****************
*   Another general rule of thumb is that pieces of information which
* are likely to be updated independently should be in different places
****************

    Suppose we decided to represent information about vice-presidents
as well.  Following Rogers' example, we would have facts

        vice_president(Name, BirthYear, In, Out).

An obvious question is, for any two people X Y in the data base, was X
born before Y or not?  With this structure, we have to write four
cases: X is a president/vice_president, Y is a president/vice_president.
But with the structure which keeps the birth year separate, we can write

        born_before(X, Y) :-
                birth_year(X, Xyear),
                birth_year(Y, Yyear),
                Xyear < Yyear.

****************
*  Another rule of thumb is that a conceptual relation should be
* represented by a single relation, not by different relations for
* different types
****************

    That's a bit vague, and there are justifiable exceptions, but
having to look in different places for the birth dates of presidents
and vice-presidents would be silly.  Rogers' does not have an example
doing this.  All I claim is that a naive reader without the benefit of
Rogers' actual presence could be misled by the example in section 9.4
into doing this.

    Almost any good data-base textbook will do a much better job of
explaining these potential problems than I'm likely to.  Look in the
index under "update anomalies" and "normal forms".  Read Kent's book
"Data and Reality".  When you know what a "join dependency" is, you'll
know why you will usually want to design them out.


                --------------------------------

                        CONCLUSION

    I've discussed at length two problems with Rogers' book.  Would
someone who thinks it is a good textbook say what they like about it
and why they think it is better than some other textbook?  I repeat
that I am not saying that Rogers' book is specially bad.  Prolog
textbooks which explain setof/3 and bagof/3 lucidly and accurately are
vanishingly rare, and the problem of examples seems to apply to most
textbooks.  Do you think these aspects matter?  Would anyone teaching a
Prolog course like to say what text(s) s/he is using, why, and what
supplementary material is needed?

    Are there any good elementary books on data base design?  Anyone
got a favourite they'd like to recommend?

------------------------------

End of PROLOG Digest
********************