Introduction to Prolog

(Credits: This material comes from Markus Triska and Thorsten Joachims and John Fisher, with extensions by Tim Menzies. We also use material from www.dataminingtools.net. )

Prolog is the Fortran of logic programming: it was the first of its kind, it was much criticised, and it is still widely used.

George Santayana: "Those who do not remember the past are condemned to repeat it."

Tim Menzies: "Those who do not study Prolog are condemned to repeat it."

The ABCs of Logic Programming

Everything is a term. Terms are are either variable or nonvar. Nonvars are either compound or atomic (and these divide into atoms or numbers).

Term
   Variable
   NonVar
      Compound 
      Atomic
         Atom
         Number

Variables are empty slots- boxes, waiting to be filled.

Terms have an functor and an arity. E.g. the following term has a functor/arity of emp/3:

emp(tim,30,male).
Terms contain terms (recursively), one for each arity; e.g.
emp(name(tim), age(30), sex(male))

Here:

Terms with an arity of zero are atoms. For example, in the above, the atoms are:

emp
name
tim
age
sex
male
The other atomic terms are numbers (integers or floats).

Terms Build Predicates

Think of them as tree structures:

emp(tim, 23, male)

emp(tim, 23, male)
    emp/3
        tim
        23
        male

2+3

2+3
    (+)/2
        2
        3

10-10*3

10-10*3
    -/2
        10
            * / 2
                10
                3

- x

-x
    -/1
        x

+ y

+y
    +/1
        y

age(Dob,Age) :- now(Now), Age is Now - Dob.

age(A, B):-now(C), B is C-A
    :-/2
            age/2
                A
                B
            ,/2
                    now/1
                        C
                    is/2
                        B
                            -/2
                                C
                                A

:- now(Now), print(Now))

:-now(A), print(B)
    :-/1
            ,/2
                    now/1
                        A
                    print/1
                        B

Predicates, Are Not Functions

Terms are used to build predicates.

And while functions return some output, predicates only return yes or no. If "yes", then as a side effect, they may add bindings to variables.

The Standard "Family" Database Example

(Adapted from Robert F. Rossa: rossa@csm.astate.edu).

Prolog programs as databases

We show below a Prolog program consisting entirely of simple clauses or facts. This set of facts is a database about a family. Note that all the gender facts are grouped together, then all the father facts, then all the mother facts. Many versions of Prolog demand that similar clauses be grouped together.

/* family.pl - a simple database for a family */
gender(al,male).
gender(anne,female).
gender(arthur,male).
gender(amber,female).
gender(boris,male).
gender(barbara,female).
gender(betty,female).
gender(buck,male).
gender(carla,female).
gender(charles,male).
gender(cora,female).
gender(cuthbert,male).
gender(cecilia,female).
gender(calvin,male).

father(arthur,buck).
father(al,buck).
father(amber,buck).
father(anne,boris).
father(barbara,charles).
father(betty,cuthbert).
father(boris,charles).
father(buck,calvin).

mother(al,barbara).
mother(anne,betty).
mother(arthur,barbara).
mother(amber,barbara).
mother(boris,carla).
mother(barbara,carla).
mother(betty,cora).
mother(buck,cecilia).

We can load this database into a Prolog interpreter.

swipl -f family.pl

The interpreter waits for queries. Every query must end with a period. When we query the interpreter, it will give us the first answer it finds, based on the facts in the database. If we want more answers, we enter a semicolon. If we don't want any more answers to that query, we just enter a carriage return. Thus consider the sequence that follows.

?- father(X,Y).
X=arthur,
Y=buck;

X=al,
Y=buck;

X=amber,
Y=buck;

X=anne,
Y=boris;

X=barbara,
Y=charles;

X=betty,
Y=cuthbert;

X=boris,
Y=charles;

X=buck,
Y=calvin

no
?-

The "no" simply indicates that there were no more answers. Prolog will answer "yes" or "no" depending on the truth or falsehood of a simple query. Thus:

?- mother(al,betty).
no
?- mother(al,barbara).
yes
?- 
We can build more complex queries like the following, to find a grandmother of al.
?- mother(al,X),mother(X,Y).
X=barbara,
Y=carla

yes
?-
By now you have observed that identifiers that start with an upper case letter are variables, and those that start with lower case letters are constants.

Rules

We can add some intelligence to our facts by adding a clause that tells how to figure out from the simple clauses whether Y is a parent of X. The rules we add are
parent(X,Y) :- father(X,Y).
parent(X,Y) :- mother(X,Y).
The symbol ':-' should be read as "if". Now we can query as follows.
?- parent(al,Y).
Y=buck;

Y=barbara

yes
?-
We can now build a rule that uses the parent rules.
grandfather(X,Y) :- parent(X,Z),father(Z,Y).
and ask questions like
?- grandfather(anne,Y).
Y=charles;

Y=cuthbert;

no
?- 

The scope of variables is one clause; there is no such thing as a global variable.

Backtracking

When Prolog tries to answer a query, it goes through a matching process. It tries to match the predicates on the right-hand side of the rule from left to right, binding variables when it finds a match. If a match fails, it may be because of the bondings in matches to the left. In that case, the interpreter backtracks - it returns to the nearest previous binding and tries to find a different match. If that fails, it backtracks some more.

Now we can use the builtin trace predicate to see what happens as one of our rules is matched.

% swipl -f family.pl  -g true
/Users/timm/svns/wisp/var/timm/10/310/src/prolog/4/a/family.pl compiled 0.00 sec, 4,872 bytes
?- spy(grandfather).
% Spy point on grandfather/2
true.

[debug]  ?-  grandfather(al,X).
   Call: (7) grandfather(al, _G313) ? creep
   Call: (8) parent(al, _L175) ? creep
   Call: (9) father(al, _L175) ? creep
   Exit: (9) father(al, buck) ? creep
   Exit: (8) parent(al, buck) ? creep
   Call: (8) father(buck, _G313) ? creep
   Exit: (8) father(buck, calvin) ? creep
   Exit: (7) grandfather(al, calvin) ? creep
X = calvin ;
   Redo: (8) parent(al, _L175) ? creep
   Call: (9) mother(al, _L175) ? creep
   Exit: (9) mother(al, barbara) ? creep
   Exit: (8) parent(al, barbara) ? creep
   Call: (8) father(barbara, _G313) ? creep
   Exit: (8) father(barbara, charles) ? creep
   Exit: (7) grandfather(al, charles) ? creep
X = charles.

[debug]  ?- 

Note that rules in the database are tried in the order they appear in the database.

We can make Prolog print out all the answers to a question by deliberately making it fail, using the fail predicate.

?- grandfather(X,Y),write('X='),write(X),write(', Y='),write(Y),nl,fail.
X=arthur, Y=calvin
X=al, Y=calvin
X=amber, Y=calvin
X=anne, Y=charles
X=al, Y=charles
X=anne, Y=cuthbert
X=arthur, Y=charles
X=amber, Y=charles
fail
?- 

Natural Langauge Processing

Prolog has a shorthand for writing grammar rules:

sentence         -->  nominal_phrase, verbal_phrase.
verbal_phrase    -->  modlist,verb.
nominal_phrase   -->  article, noun.
article          -->  [the].
noun             -->  [dog].
verb             -->  [runs].
verb             -->  [barks].
modlist          -->  [].
modlist          -->  [sadly], modlist.

(Note that modlist is zero or more "sadly"s.).

If we load this into PROLOG, we get

swipl -f english.pl
?- listing(sentence).
sentence(A, C) :-
	nominal_phrase(A, B),
	verbal_phrase(B, C).

true.

?- listing(verb).
verb([runs|A], A).
verb([barks|A], A).

true.

?- 

That is, PROLOG interprets grammars as rules. Terminals (like runs) are assumed to be members of a list.

We can ask PROLOG now, what sentences are valid in this grammar:

?- sentence(X,[]).
X = [the, dog, runs] ;

X = [the, dog, barks];

no
?- 

We can also ask, is a sentence valid:

?- sentence([the,dog,runs],X).
X = [] 
.

?- sentence([the,dog,sadly,sadly,sadly,runs],X).
X = [] 
.

?- sentence([the,dog,flies],X).
false.

?- 

Finding one Solution (the Cut)

Condiser the following Prolog program.

part(a). part(b). part(c). 
red(a). black(b). 

color(P,red) :- red(P),!. 
color(P,black) :- black(P),!. 
color(P,unknown). 

The cut predicate "!" prunes away all the choice points active at the time of reaching that predicate. The cut goal succeeds whenever it is the current goal, AND the derivation tree is trimmed of all other choices on the way back to and including the point in the derivation tree where the cut was introduced into the sequence of goals.

(So it is called "cut" since it prunes away sections of the possible proof tree.) The intention is to determine a color for a part based upon specific stored information, or else conclude that the color is 'unknown' otherwise. A derivation tree for goal ?- color(a,C) is

which corresponds with

?- color(a,C). 
C = red 
and a derivation tree for goal ?- color(c,C) is

which corresponds with

?- color(c,C). 
C = unknown 

The Prolog cut is a procedural device for controlling goal satisfaction. The use of cut affects the meanings of programs. For example, in the 'color' program, the following program clause tree says that 'color(a,unknown)' should be a consequence of the program:

The cut in the program enables Prolog to "avoid" this answer. It would be difficult to modify the program clause tree definition (for program consequences) to reflect the procedural meaning of cut.

Cut is evil, sometimes a ncesseary evil, but it is still evil. It makes it harder to reason about a program. One way to reduce using it is the "once" predicate that finds the first solution, then stops.

once(X) :- call(X), !.

We will use "once", below.

Finding no Solutions (negatation as Failure)

The negation-as-failure 'not' predicate could be defined in prolog as follows:

not(P) :- call(P), !, fail. 
not(_). 

The goal ?-call(P) forces an attempt to satisfy the goal P. Most Prolog interpreters have a built-in version of this predicate. Quintus, SWI, and many other prologs use '\+' rather than 'not'.

Another way one can write the 'not' definition is using the Prolog implication operator '->' :

not(P) :- (call(P) -> fail ; true) 

The body can be read "if call(P) succeeds (i.e., if P succeeds as a goal) then fail, otherwise succeed". The semicolon ';' appearing here has the meaning of exclusive logical-or.

There will be numerous uses of 'not' in subsequent programs in the chapter.

It is important to realize that in a goal ?-not(g), if g has variables, and some instance of these variables lead to satisfaction of g, then ?-not(g) fails. For example, consider the following 'bachelor program:

bachelor(P) :- male(P), not(married(P)). 

male(henry). 
male(tom). 

married(tom). 

Then

?- bachelor(henry). 
yes 
?- bachelor(tom). 
no 
?- bachelor(Who). 
Who= henry ; 
no 
?- not(married(Who)). 
no. 

The first three responses are correct and as expected. The answer to the fourth query might have been unexpected at first. But consider that the goal ?-not(married(Who)) fails because for the variable binding Who=tom, married(Who) succeeds, and so the negative goal fails. Thus, negative goals ?-not(g) with variables cannot be expected to produce bindings of the variables for which the goal g fails.

Finding All Solutions

In procedural systems, you write lottsa forloops.

In logic programming, you write a predicate that returns ONE solution, then call that in a special way to get ALL the solutions.

findall(Instance, Goal, List)

findall/3 succeeds if List can be unified with the list of all instances of Instance making Goal provable. For example, findall(X, a(X), [1, 2, 3]) is true if the logicbase contains the following clauses for a:

a(1).
a(2).
a(3).
If Goal cannot be proved for any values of Instance, then List is unified with the empty list []. findall/3 is generally used to generate lists from logicbase entries, so for example it might be used as follows (with the logicbase shown above):
?- findall(X, a(X), L).
L = [1, 2, 3]

Consider these facts in the logicbase:

fact(bird, duck).
fact(sound, quack).
fact(color, white).
The terms in the list take the form of the first argument, as in this example:
?- findall(Name:Value, fact(Name, Value), List).
List = [bird:duck, sound:quack, color:white]

Returning to the above example:

finds(A, B, D) :-
        findall(C, find(A, 0, B, C), D).

?- demo1(X), finds(numberp,X,L).
X = emp(tim, 23, male),
L = [23] ;

X = [a, b, c],
L = [] ;

X = 2+3,
L = [2, 3] ;

X = 10-10*3,
L = [10, 10, 3] ;

X = -x,
L = [] ;

X = +y,
L = [] ;

X = (age(_G284, _G285):-now(_G290), _G285 is _G290-_G284),
L = [] ;

X = (:-now(_G286), print(_G286)),
L = [].

For the geeks amongst you, see also bagof/3 and setof/3: http://www.amzi.com/manuals/amzi7/pro/ref_execution.htm.

List Processing

Prolog lists have heads and tails (think "car" and "cdr").

[a, b, c]

[a, b, c]
    . / 2
        a
            . / 2
                b
                    . / 2
                        c
                        []

So we can write a list member predicate as follows:

member(A, [A|_]).
member(A, [_|B]) :-
        member(A, B).

So, of course,...

?- member(a,[c,b,a]).
true

But Prolog can bactrack to find all repeated members of a list:

?-    member(a,[c,a,b,a]).
true ;
true 

Also, we can find all members of a list:

?- member(X,[a,b,c]).
X = a;
X = b;
X = c

Also, Prolog is a relational language. Variables are not inputs and outputs but concepts woven together by constraints. Also, we only need to partially specify the I/O to query those constraints:

?-  member(name=What, [age=20,shoesize=12,name=tim]).
What = tim .

(If this is causing stress, just chant to yourself: in Prolog, we don't code; rather, we draw the shape of the solution.)

This can get really funky. Here's the standard definition of how to append two lists:

append([], A, A).
append([A|B], C, [A|D]) :-
        append(B, C, D).

Which works in the expected way:

?- append([a,b,c],[d,e,f],Out).
Out = [a, b, c, d, e, f].

But any of these arguments can be left unspecified to achieve neat results:

; find the third thing in a list
?- append([ _, _, Third], _, [alice,john,mike,tim,veronica,wendy]).
Third = mike

; find the list before "tim"
?- append(Before, [tim | _], [alice,john,mike,tim,veronica,wendy]).
Before = [alice, john, mike]

; find the list after "tim"
?- append(_, [tim | After], [alice,john,mike,tim,veronica,wendy]).
After = [veronica, wendy]

You can even write "member" using "append":

member1(X,L) :- append(_,[X|_],L).

?- member1(a,[b,c,a]).
true .

?- member1(X,[b,c,a]).
X = b ;
X = c ;
X = a ;
fail.

More List Processing

Before we look at more PROLOG list processing, lets recall the general structure of any recursive traversal:

(defun addr (l) 
  (cond ((null l) 0)      ; termination
        ((numberp x)  x)  ; base case
        (t (let ((sum 0))
             (dolist (one x sum)
               (incf sum (addr one)) ; recursive case
            )))))

Prolog version

addr(L,Out) :- % helper function. initialize a counter to zero
   addr(L,0,Out). 

addr([],Out,Out).     % base case: empty list so return the accumulator
addr([H|T],In,Out) :- 
   addr(H,In,Tmp),    % recurse on the head
   addr(T,Tmp,Out).   % recurse on the tail
addr(X,In,Out) :-     % base case
   number(X),
   Out is In + X.

Note the is/2 predicate: used for maths. E.g.

?- X is 1 + 2 / 3 * 4.
X = 3.6667

?- X is random(3)
X = 2 . % some random number from 0 .. (3-1)

?- 0 is 22 mod 2.
true. % standard test for even numbers

Demo

?- addr([1,[2,[3,4],5],6,7],X).
X = 28 .

More complex example: extracting any random item from a list.

oneLess(L,One,Less) :- 
	length(L,N0),     % e.g. ?- length([a,b,c],N). N = 3. 
        N is random(N0),  % some random number 0 ... N0 - 1
        oneLess1(N,L,One,Less). % split the list on item N

oneLess1(0,  [One|Less],One,Less).    % termination
oneLess1(N0, [H|T],One, [H|Rest]) :-  % base case handled in the head 
	N0 > 0, 
        N is N0 - 1, 
        oneLess1(N,T,One,Rest).       % recursive case

oneLess/1 is like a rocket countdown. Each recursive call decrements "N" by one. At zero, we stop to form "One" and "Less" from the head and tail of the list.

So now we can shuffle a list in the usual way.

shuffle([],[]).                     % termination
shuffle([H|T], [One|Rest]) :- 
	oneLess([H|T], One, Less),  % base case: head of list is anything
        shuffle(Less,Rest).         % recusive case: shuffle what's left

For example, we can can rearrange "prolog" into another random order as follows:

shuffle("prolog",X), format('~s\n',[X]).
X = golorp

(Exercise for the reader: what does the above say about how Prolog stores double quoted strings?).

Remember that name: "golorp". We'll use it later.

Parsing

Prolog is a natural tool for recursive descent parsing. Every term is a nested set of terms. Remember these:

demo1(emp(tim,23,male)).
demo1([a,b,c]).
demo1(2+3).
demo1(10 - 10 * 3).
demo1(- x). 
demo1(+ y).
demo1((age(Dob,Age) :- now(Now), Age is Now - Dob)).
demo1((:- now(Now), print(Now))).

How can we run over these to find (e.g.) all the numbers found in the above terms? Prolog has a nifty predicate =.. that is hammer that breaks up terms. E.g.

?- emp(tim,23,male) =.. L.
L = [emp,tim,23,male].

?- 10 - 10 * 3 =.. L.
L = [-, 10, 10*3].

So, to recurse over any Prolog term, just convert all terms to lists using =.. and write a recursive list traversal. That is, we convert the problem of recursively parsing terms (hard!) to one of recursively parsing lists (easy!).

find(What,N,Term,Found) :- 
    call(What,N,Term,Found).
find(What,N,Term,Found) :-
    nonvar(Term),
    Term =.. [H1,H2|T],      % lists must be at least two items long.
    member(Next,[H1,H2|T]),
    find(What,N+1,Next,Found).

(Exercise for the reader: why do we only recurse on lists with at least two items?)

There are a few tricks in the above. The call/4 command performs double duty:

For example, here's a predicate that checks for numbers and, if they exist, just returns them unmodified:

numberp(_,N,N) :- number(N).

Demo:

?- demo1(X),find(numberp,0,X,Y).
X = emp(tim, 23, male),
Y = 23 ;

X = 2+3,
Y = 2 ;

X = 2+3,
Y = 3 ;

X = 10-10*3,
Y = 10 ;

X = 10-10*3,
Y = 10 ;

X = 10-10*3,
Y = 3 ;
fail.

And, of course, we could modify the stuff we find:

mult(M,_,N,NtimesM) :- number(N), NtimesM is M * N.

?- demo1(X),find(mult(10),0,X,Y).
X = emp(tim, 23, male),
Y = 230 ;

X = 2+3,
Y = 20 ;

X = 2+3,
Y = 30 ;

X = 10-10*3,
Y = 100 ;

X = 10-10*3,
Y = 100 ;

X = 10-10*3,
Y = 30 ;
fail

We could use this to print all the contents:

blurt(N,Term,Term) :-
    tab(4*N),
    numbervars(Term,1,_),
    print(Term),nl.

?- demo1(X),write('----------------------------\n'), 
   find(blurt,0,X,_),fail.

----------------------------
emp(tim, 23, male)
    emp
    tim
    23
    male
----------------------------
[a, b, c]
    .
    a
    [b, c]
        .
        b
        [c]
            .
            c
            []
----------------------------
2+3
    +
    2
    3
----------------------------
10-10*3
    -
    10
    10*3
        *
        10
        3
----------------------------
-x
    -
    x
----------------------------
+y
    +
    y
----------------------------
age(B, C):-now(D), C is D-B
    :-
    age(B, C)
        age
        B
        B
    now(B), C is B-D
        ,
        now(B)
            now
            B
        B is C-D
            is
            B
            B-C
                -
                B
                B
----------------------------
:-now(B), print(B)
    :-
    now(B), print(B)
        ,
        now(B)
            now
            B
        print(B)
            print
            B
fail.

Now just pause here and think: how would we do this is a standard parsing paradigm? Scanner, lexical analysis, tree traversal, etc etc etc. You gotta say, this is all pretty succinct.

Finding all the LHS Symbols

How would we find all the LHS symbols in the following grammar?

sentence --> nounphrase,verbphrase.  

nounphrase --> [the, boy].             
nounphrase --> [the,girl].           

verbphrase --> verb,modlist,adverb.

verb --> [runs].                
verb --> [walks].             

modlist -->  [].                    
modlist --> [very],modlist.     

adverb --> [quickly].          
adverb --> [slowly].          

One methd, which is WRONG is to use clause to walk over all the rules reachable from "sentence", and print the functor of their heads. This crashes, for interesting reasons:

lhs(N,X,Lhs) :- 
     nonvar(X), 
     mine(X,_,Body), 
     lhs1(N,X,Body,Lhs).

mine(X,F,Body) :- 
    not(builtIn(X)), 
    clause(X,Body), 
    X =.. [F|_].

builtIn(X) :- 
    predicate_property(X,built_in).

lhs1(_,Head,_,Lhs) :- 
     Head =.. [Lhs|_].
lhs1(N,_,Body,Lhs) :-  % recursive call to fund
     find(lhs,N+1,Body,Lhs). 

?- find(lhs,0,sentence(_,[]),X).
X = sentence ;
X = nounphrase ;
X = nounphrase ;
X = verbphrase ;
X = verb ;
X = verb ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist ;
X = modlist 

See the problem? The Modlist rule references itself so the code loops forever.

The solution is two-fold:

  1. Rewrite the grammars so there is one rule per LHS
    sentence --> nounphrase,verbphrase.  
    
    nounphrase --> [the, boy]
                 | [the,girl].           
    
    verbphrase --> verb,modlist,adverb.
    
    verb --> [runs] 
           | [walks].             
    
    modlist --> [] 
              | [very],modlist.     
    
    adverb --> [quickly] 
             | [slowly].           
    
  2. Add a working memory variable to "find" and "lhs" that tracks what rules we have seen so far. Do not recurse into a rule unless it is new.
    find(What,N,Term,W0,W,Found) :- 
        call(What,N,Term,W0,W,Found).
    find(What,N,Term,W0,W,Found) :-
        nonvar(Term),
        Term =.. [H1,H2|T], 
        member(Next,[H1,H2|T]),
        find(What,N+1,Next,W0,W,Found).
    
    lhs(N,X,W0,W,Lhs) :- 
    	nonvar(X), 
    	mine(X,Functor, Body),
    	not(member(Functor,W0)),
    	lhs1(N,X,Body,[Functor|W0],W,Lhs).
    
    lhs1(_,Head,_,W,W,Lhs) :- Head =.. [Lhs|_].
    lhs1(N,_,Body,W0,W,Lhs) :- find(lhs,N+1,Body,W0,W,Lhs).
    

The above machinery lets us do the obvious thing:

finds(What,N,Term,W,Founds) :-
    findall(Found,
             find(What,N,Term,W,_,Found),
            Founds).

?- finds(lhs,0,sentence(_,[]),[],F).
F = [sentence, nounphrase, verbphrase, verb, modlist, adverb].

Extra Arguments in Grammars

What is wrong with this grammar?

s --> np, vp. 
np --> det, n. 
np --> pro. 
vp --> v, np. 
vp --> v. 
det --> [the].            
det --> [a]. 
n --> [woman].        
n --> [man].  
v --> [shoots]. 
pro --> [he]. 
pro --> [she]. 
pro --> [him]. 
pro --> [her]. 

Seems to work ok...

?- s([she,shoots,him],[ ]). 
yes 
?- s([a,woman,shoots,him],[ ]). 
yes 

But these sentences are odd...

?- s([a,woman,shoots,he],[ ]). 
yes 
?- s([her,shoots,a,man],[ ]). 
yes 
s([her,shoots,she],[ ]). 
yes 

The DCG ignores some basic facts about English

It is obvious what we need to do:

How do we do this? Version 1 (verbose, naive):

s --> np_subject, vp. 
np_subject --> det, n.                  
np_object --> det, n. 
np_subject --> pro_subject.        
np_object --> pro_object. 
vp --> v, np_object. 
vp --> v. 
det --> [the].               
det --> [a]. 
n --> [woman].        
n --> [man].  
v --> [shoots]. 
pro_subject --> [he]. 
pro_subject --> [she]. 
pro_object --> [him]. 
pro_object --> [her]. 

Nice way using extra arguments:

s --> np(subject), vp. 
np(_) --> det, n.                   
np(X) --> pro(X). 
vp --> v, np(object). 
vp --> v. 
det --> [the].               
det --> [a]. 
n --> [woman].        
n --> [man].  
v --> [shoots]. 
pro(subject) --> [he]. 
pro(subject) --> [she]. 
pro(object) --> [him]. 
pro(object) --> [her]. 

Better:

?- s([she,shoots,him],[ ]). 
yes 
?- s([she,shoots,he],[ ]). 
no 
?- 

What is really going on? Recall that the rule:

s --> np,vp. 
is really syntactic sugar for:
s(A,B):- np(A,C), vp(C,B). 
The rule
s --> np(subject),vp. 
translates into:
s(A,B):- np(subject,A,C), vp(C,B). 
so, in effect, we have added a guard to the computation.

Side-effect Calculations

It turns out that tree-traversal and computation are closely linked. E.g. descending into a tree based on a certain symbol is the same a conditional block with a guard.

PROLOG adds a {X} construct to its grammar rules that let you do primitive computations.

number(0)    -->  [zero].
number(N)    -->  digit(H), [hundred],
    and_tens(T), {N is H * 100 + T}.
number(N)    -->  sub_tens(N).

and_tens(0)  --> [].
and_tens(N)  --> [and], sub_tens(N).

sub_tens(N)  --> digit(N).
sub_tens(N)  --> teen(N).
    sub_tens(N)  --> tens(T), and_digit(D), {N is T + D}.

and_digit(0) --> [].
and_digit(N) --> digit(N).

digit(1) --> [one].      teen(10) --> [ten].
digit(2) --> [two].      teen(11) --> [eleven].
digit(3) --> [three].    teen(12) --> [twelve].
digit(4) --> [four].     teen(13) --> [thirteen].
digit(5) --> [five].     teen(14) --> [fourteen].
digit(6) --> [six].      teen(15) --> [fifteen].
digit(7) --> [seven].    teen(16) --> [sixteen].
digit(8) --> [eight].    teen(17) --> [seventeen].
digit(9) --> [nine].     teen(18) --> [eighteen].
                         teen(19) --> [nineteen].

tens(20) --> [twenty].
tens(30) --> [thirty].
tens(40) --> [forty].
tens(50) --> [fifty].
tens(60) --> [sixty].
tens(70) --> [seventy].
tens(80) --> [eighty].
tens(90) --> [ninety].

The associated program now responds to queries of the following type:

[user] ?- number(Value, [ two, hundred, and, twenty, three ], []). 

Value = 223 

yes

Besides performing this syntactical analysis and calculating the value, the program is also capable of providing an appropriate verbal expression for a given value (synthesis).

[user] ?- number(101, X, []). 

X = [one,hundred,and,one]

Database manipulation

Prolog has four database manipulation commands:

Suppose we now give this command:

?- assert(happy(mia)).
It succeeds (assert commands always succeed). If we now give the command:
?- listing(happy).

we get the listing:

happy(mia).

That is, the database contains the fact we asserted. Suppose we then made four more assert commands:

?- assert(happy(vincent)).
?- assert(happy(marcellus)).
?- assert(happy(butch)).
?- assert(happy(vincent)).
Suppose we then ask for a listing:
?- listing(happy).
happy(mia).
happy(vincent).
happy(marcellus).
happy(butch).
happy(vincent).
yes

So far, we have only asserted facts into the database, but we can assert new rules as well. Suppose we want to assert the rule that everyone who is happy is naive. That is, suppose we want to assert that:

naive(X) :- happy(X).

We can do this as follows:

?- assert( (naive(X) :- happy(X)) ).

To remove all of our assertions we can use a variable:

retract(happy(X)).
X = mia ;
X = butch ;
X = vincent ;
No

If we want more control over where the asserted material is placed, there are two variants of assert, namely:

For example, suppose we start with an empty database, and then we give the following command:

assert( p(b) ), assertz( p(c) ), asserta( p(a) ).

Then a listing(p) reveals that we now have the following database:

p(a).
p(b).
p(c).

Like cuts, assert is evil. Try to avoid using them. Having said that, sometimes evil is necessary. For example, here is the standard definition of findall/3:

findall( X, Goal, Xlist)  :-
  call( Goal),                         % Find a solution
  assertz( queue(X) ),                 % Assert it
  fail;                                % Try to find more solutions
  assertz( queue(bottom) ),            % Mark end of solutions
  collect( Xlist).                     % Collect the solutions 

collect( L)  :-
  retract( queue(X) ), !,              % Retract next solution
  ( X == bottom, !, L = []             % End of solutions?
    ;
    L = [X | Rest], collect( Rest) ).  % Otherwise collect the rest 

(If you do not understand this code, that is because of the evils of "cut".)

For example, and this example comes from above, suppose we have these facts for "a":

a(1).
a(3).
a(2).

Then

:- findall(X,a(X),L).
L= [1,3,2].

Interpreters

Prolog Interpreters (of Prolog)

Informally, an interpreter is a program that evaluates programs. An interpreter for a language similar or identical to its own implementation language is called meta-interpreter (MI). Prolog is well-suited for writing MIs: Prolog programs can be naturally represented as Prolog terms and are easily inspected and manipulated using built-in mechanisms.

Consider a Prolog program:

natnum1(0).
natnum1(s(X)) :-
        natnum1(X).

Prolog can evaluate Prolog code dynamically:

?- Goal = natnum1(X), Goal.

Goal = natnum1(0)
X = 0 ;

Goal = natnum1(s(0))
X = s(0)

Yes

You can make the call explicit using the built-in call/1 predicate, yielding the equivalent query "?- Goal = natnum1(X), call(Goal).". The call/n (n > 1) family of predicates lets you append additional arguments to Goal before it is called.

Note that The definition of natnum1/1 consists of two clauses, the first of which degenerated into a fact that is implicitly treated as if it were stated as

natnum1(0) :- true.

The Prolog built-in clause/2 allows inspection of the predicate's definition:

?- clause(natnum1(Z), Body).

Z = 0
Body = true ;

Z = s(_G254)
Body = natnum1(_G254) ;

No

The two solutions found on backtracking correspond to the two clauses of the predicate's definition.

Another example:

complicated_clause(A) :-
        goal1(A),
        goal2(A),
        goal3(A).

?- clause(complicated_clause(Z), Body).

Z = _G187
Body = goal1(_G187), goal2(_G187), goal3(_G187) ;

No

The body of complicated_clause/1 is represented by a compound term with functor "," and arity 2, whose arguments are either goals or compound terms of the same structure.

We can thus define an meta-interpreter for pure Prolog programs:

prolog(true).
prolog((A,B)) :-
        prolog(A),
        prolog(B).
prolog(Goal) :-
        Goal \= true,
        Goal \= (_,_),
        clause(Goal, Body),
        prolog(Body).

This is often called the vanilla Prolog meta-interpreter because there's nothing special to it. They serve as skeletons for extensions.

We can use this MI to run our example program:

?- prolog(natnum1(X)).

X = 0 ;

X = s(0) ;

X = s(s(0))

Yes

We had to guard the third clause from matching true/0 or conjunctions to prevent run-time errors resulting from calling clause/2 with these arguments. This is ugly.

Another problem with the above is that, at least in SWI Prolog, calling clause/2 on certain builit-in predictess (like the maths predicate is/2, or the true predicate true/0) causes a crash.

To fix both these problems, we need to look before we leap.

prolog(Goal) :- once(look(Goal,How)), leap(How).

look(true,  skip    ).
look((X,Y), and(X,Y))
look(X,     call(X) ) :- predicate_property(X,built_in).
look(X,     do(X)   ).

leap(skip    ).
leap(and(X,Y)) :- prolog(X), prolog(Y).
leap(call(X) ) :- call(X).
leap(do(X)   ) :- clause(X,Body), prolog(Body).

The once/1 predicate returns the top-most result and never backtracks to anything else.

This works, in the expected way:

demo :-
	s(Sentence1,[]),
	prolog(s(Sentence2,[])),
	format('1 : ~p\n2 : ~p\n',[Sentence1,Sentence2]).
	
1 : [the, woman, shoots, the, woman]
2 : [the, woman, shoots, the, woman]

Ok, so what? All we've achieved is to write an interpreter of Prolog that does the same thing as raw Prolog. Well, the power of this method is that we can change how Prolog executes. For example, suppose we wanted to return random sentences from our grammar? First, we need something that can randomly shuffle the contents of a list. And we have that (see shuffle/2, above).

Next, we need something that can find all the different ways we can prove a clause, and then jumble up that order:

clauses(Head,Bodies) :-
	findall(Head/Body, clause(Head,Body), Bodies0),
	shuffle(Bodies0,Bodies).

With all that in place, we only need adjust the intepreter such that when we interpret a clause body, we use a randomly selected body. We call that interpreter "golorp" since it rearranges things into a random order:

golorp(Goal) :- once(look(Goal,How)), leap(How).

look(true,  skip).
look((X,Y), and(X,Y)).
look(X,     call(X)) :- predicate_property(X,built_in).
look(X,     do(X,L)) :- clauses(X,L).

leap(skip).
leap(and(X,Y)) :- golorp(X), golorp(Y).
leap(call(X))  :- call(X).
leap(do(X,L)) :- member(X/Body,L), golorp(Body).

Now, when we run the demo code, we get as many random sentences as we like:

demo :- forall(between(1,10,_), demo1).

demo1 :-
	s(Sentence1,[]),
	golorp(s(Sentence2,[])),
	format('\n1 : ~p\n2 : ~p\n',[Sentence1,Sentence2]).

% :- demo.
	
1 : [the, woman, shoots, the, woman]
2 : [the, woman, shoots]

1 : [the, woman, shoots, the, woman]
2 : [she, shoots, him]

1 : [the, woman, shoots, the, woman]
2 : [she, shoots]

1 : [the, woman, shoots, the, woman]
2 : [she, shoots]

1 : [the, woman, shoots, the, woman]
2 : [he, shoots, the, man]

1 : [the, woman, shoots, the, woman]
2 : [he, shoots]

1 : [the, woman, shoots, the, woman]
2 : [she, shoots, her]

1 : [the, woman, shoots, the, woman]
2 : [he, shoots, the, woman]

1 : [the, woman, shoots, the, woman]
2 : [the, man, shoots, a, man]

1 : [the, woman, shoots, the, woman]
2 : [the, woman, shoots]

Note the power of knowledge-based programming. We did not change the structures we were interpreting (the natural language grammar). Rather, we only change the interpreter.

Some More Details

Finally, here is a larger version of the above system that handles some other cases such as not/2 and disjunctions. It also complains if we try to prove an unbound goal (which is an error in Prolog):

% golorp.pl
golorp(X) :- once(look(X,Y)), leap(Y).

look(X,     barph(var)) :- var(X).
look(true,  skip).
look((X;Y), or(L)) :- or2List((X;Y),L0), shuffle(L0,L).
look((X,Y), and(X,Y)).
look(X,     do(X)) :- predicate_property(X,built_in).
look(\+ X,  not(X)).
look(X,     anybody(X,L)) :- clauses(X,L).

leap(skip).
leap(barph(Why))   :- format('% ERROR: ~p.~n',Why),fail.
leap(do(X))        :- call(X).
leap(and(X,Y))     :- golorp(X),  golorp(Y).
leap(not(X))       :- \+ golorp(X).
leap(or(L))        :- member(X,L), golorp(X).
leap(anybody(X,L)) :- member(X/Body,L), golorp(Body).

or2List((X;Y),[X|Rest]) :- !,or2List(Y,Rest).
or2List(Y,    [Y]).

We execute it on the following grammar:

% english.pl
sentence --> nounphrase,verbphrase.  

nounphrase --> [the, boy].             
nounphrase --> [the,girl].           

verbphrase --> verb,modlist,adverb.

verb --> [runs].                
verb --> [walks].             

modlist -->  [].                   
modlist --> [very],modlist.     

adverb --> [quickly].          
adverb --> [slowly].           

And, here is our example:

demo :- forall(between(1,10,_),demo1).

demo1 :-
	sentence(Sentence1,[]),
	golorp(sentence(Sentence2,[])),
	format('\n1 : ~p\n2 : ~p\n',[Sentence1,Sentence2]).

Which produces this output:

% :- [english,golorp].
% :- demo.

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, very, very, very, very, very, very, slowly]

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, very, slowly]

1 : [the, boy, runs, quickly]
2 : [the, boy, runs, slowly]

1 : [the, boy, runs, quickly]
2 : [the, girl, walks, slowly]

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, very, very, slowly]

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, very, quickly]

1 : [the, boy, runs, quickly]
2 : [the, girl, runs, very, very, quickly]

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, slowly]

1 : [the, boy, runs, quickly]
2 : [the, girl, runs, very, very, slowly]

1 : [the, boy, runs, quickly]
2 : [the, boy, walks, very, slowly]

Prolog Interpreters (of Other Langauges)

Here is an embedded ALGOL-like language in Prolog. By Maud`Dib.

Managing state:

change_member(_, N, [], [N]).
change_member(O, N, [O|XS], [N|XS]).
change_member(O, N, [X|XS], [X|YS]) :- change_member(O, N, XS, YS).

Define the language:

:- op(900, xfy, :=).
:- op(100, fx, [if, then, else]).
:- op(125, fx, [while,  return]).
:- op(125, yfx, do).

Running a block:

run({Block},EnvI, O, Ret) :-
  run(Block, EnvI, O, Ret).

Do things, return the result of doing the last one:

run((First; Second), EnvI, O, Ret) :-
  run(First, EnvI, M, _), 
  run(Second, M, O, Ret).

Do assignment.

run(Place := Expr, EnvI, O, void) :-
  eval(Expr, Value, EnvI), 
  change_member(Place = _, Place = Value, EnvI, O), !.

Do if-then-else..

run(if(Cond) then {This} else {That}, EnvI, O, Ret) :-
  eval(Cond, Value, EnvI),
  ( Value = true -> Body = This ; Body = That ),
  run(Body, EnvI, O, Ret).

Do while.

run(while(Cond) do {Body}, EnvI, O, Ret) :-
  eval(Cond, Value, EnvI),
  ( Value = true -> 
    run(Body, EnvI, M, _),
    run(while(Cond) do {Body}, M, O, Ret)
  ; O = EnvI, Ret = void
  ).

Do a return value.

run(return Value, EnvI, O, Ret) :-
  eval(Value, Ret, EnvI), O = EnvI.

Evaluate stuff

eval(Var,   Val,   Env) :- member(Var = Val, Env), !.
eval(Num,   Num,   _  ) :- number(Num), !.
eval(true,  true,  _  ).
eval(false, false, _  ).

eval(X + Y, Z, Env) :- 
	eval(X, XV, Env), 
	eval(Y, YV, Env), 
	Z is XV + YV.
eval(X - Y, Z, Env) :- 
	eval(X, XV, Env), 
	eval(Y, YV, Env), 
	Z is XV - YV.
eval(X * Y, Z, Env) :- 
	eval(X, XV, Env), 
	eval(Y, YV, Env), 
	Z is XV * YV.
eval(X / Y, Z, Env) :- 
	eval(X, XV, Env), 
	eval(Y, YV, Env), 
	Z is truncate(XV / YV).

eval(X = Y, B, Env) :- 
	eval(X, XV, Env), eval(Y, YV, Env),
  	( XV = YV -> B = true ; B = false ).
eval(X < Y, B, Env) :- 
	eval(X, XV, Env), eval(Y, YV, Env),
  	( XV < YV -> B = true ; B = false ).
eval(X > Y, B, Env) :- 
  	eval(X, XV, Env), eval(Y, YV, Env),
  	( XV > YV -> B = true ; B = false ).
eval(not(P), B, Env) :- eval(P, PV, Env),
  	( PV = true -> B = false ; B = true ).

factorial

factorial(N, {i := 1;
              n := N;
              while(n > 0) do {
                i := i * n;
                n := n - 1
              };
              return i}).

%% ?- factorial(5, P), run(P, [], _, X).
%% X = 120

fibonacci

fibonacci(F, {i := 1; a := 1; b := 1;
              while(not(i = F)) do {
                a := a + b;
                b := a - b;
                i := i + 1
              };
              return b}).

%% ?- fibonacci(7, P), run(P, [], _, X).
%% X = 13

pow2

pow2(N, {i := N;
         n := 1;
         while(not(i = 0)) do {
           i := i - 1;
           n := n * 2
         };
         return n}).

%% ?- pow2(8, P), run(P, [], _, X).
%% X = 256

gcd

gcd(X, Y,
    {x := X;
     y := Y;
     while(not(x = 0)) do {
       if(x < y) then {
         y := y - x
       }
       else {
         x := x - y
       }
     };
     return y}).

%% ?- A is 2*2*5*7*13, B is 5*13*7*7, gcd(A, B, P), run(P, [], _, X).
%% A = 1820,
%% B = 3185,
%% X = 455

isqrt

isqrt(N, {q := N; p := (q + N / q) / 2;
          while(q > p) do {
            q := p;
            p := (q + N / q) / 2
          };
          return p}).

%% ?- isqrt(81, P), run(P, [], _, X).