Prolog Programming Project



Download 69.23 Kb.
Date28.05.2018
Size69.23 Kb.
#52238
Programming Languages

Prolog Programming Project


Due Wednesday, December 5th, 2001

How Prolog Works

Although Prolog is a very powerful and expressive programming environment, it is surprisingly easy to implement. All that is required is recursion, resolution, and unification.



Unification

A unifier of two terms is a set of bindings of variables that make the two terms equal. The Most General Unifier (or MGU) of two terms is the unifier that puts the smallest possible set of restrictions on the variables in the terms. For instance, the terms father(fred, X) and father(Y, sue) have an MGU of {X/sue, Y/fred}. Of course, not all terms have a unifier. There is no binding of variables to values that make the terms father(fred, john) and father(X, mike) equal. There is a very simple algorithm to find the MGU of two terms A and B:



To find the MGU of A and B, given a binding list L:

  • If A and B are both simple terms (like fred, mike, or sue), then if A=B, the MGU of A and B is L. If A != B, then there is no MGU of A and B.
    Consider the following examples:



Term A

Term B

Binding List L

MGU of A and B given L

fred

fred

{}

{}

fred

mike

{}

NONE

mike

mike

{X/a}

{X/a}




  • If A is an unbound variable that does not occur in B, then the MGU of A and B is L {A/B}.
    Consider the following examples



Term A

Term B

Binding List L

MGU of A and B given L

X

fred

{}

{X/fred}

X

father(Y,john)

{Y/mike}

{X/father(Y,john), Y/mike}

X

father(X)

{}

NONE




  • If B is an unbound variable that does not occur in A then the MGU of A and B is {B/A}.




Term A

Term B

Binding List L

MGU of A and B given L

fred

X

{}

{X/fred}

num(0)

Z

{ }

{Z/num(0)}

num(X)

X

{}

NONE




  • If A is a variable bound to the term t, then the MGU of A and B given L is the MGU of t and B given L. Consider the following examples:



Term A

Term B

Binding List L

MGU of A and B given L

X

mike

{X/mike}

{X/mike}

X

mike

{X/Y}

{X/Y, Y/mike}

X

father(a)

{X/father(Y)}

{X/father(Y), Y/a}

X

john

{X/mike}

FAIL

X

john

{X/Y, Y/Z}

{X/Y, Y/Z, Z/john}

Looking at the last example in more depth: To find the MGU of X and john given {X/Y, Y/Z}: Since X is bound to Y in {X/Y, Y/Z}, the MGU of X and john given {X/Y, Y/Z} is the MGU of Y and john given {X/Y, Y/Z}. Since Y is bound to Z in {X/Y, Y/Z}, the MGU of Y and john given {X/Y, Y/Z} is the MGU of Z and john given {X/Y, Y/Z}, which is {X/Y, Y/Z, Z/john}





  • If B is a variable bound to the term t, then the MGU of A and B given L is the MGU of A and t given L. Consider the following examples:



Term A

Term B

Binding List L

MGU of A and B given L

f(Y)

X

{X/Z}

{X/Z, Z/f(Y)}

f(Y)

X

{X/Y}

FAIL

f(a)

X

{X/f(Z)}

{X/f(Z), Z/a}




  • If A and B are complex terms (like mother(julie, rachel) or father(mike, adam)), first check to see that A and B are they same complex term. If they are, then recursively find the MGU of the first argument of the two terms, then use that binding to find the MGU of the second term, and so on. If A and B are different terms, then there is no MGU of A and B. For instance, consider the following examples:




Term A

Term B

Binding List L

MGU of A and B given L

mother(X,john)

mother(sue,Y)

{}

{X/sue, Y/john}

mother(X,X)

mother(a,b)

{}

FAIL

f(g(X,X), Y)

f(Y,g(a,a))

{}

{Y/g(X,X), X/a}

Looking at the second example above in more detail:


to find the MGU of mother(X, X) and mother(a,b) given {}:
since the two complex terms are both "mother" terms:
first, find the MGU of X and a given {}, to get {X/ a}
next, find the MGU of X and b given {X/ a}

the MGU of X and b given {X/a} is the MGU of a and b given {X/a}

which FAILS

Thus there is no MGU of mother(X, X) and mother(a,b) given {}


Looking at the third example in more detail:
to find the MGU of f(g(X,X),Y) and f(Y,g(a,a)) given {}:
since the two complex terms are both "f" terms:
first, find the MGU of g(X,X) and Y given {}, to get {Y/g(X,X)}
next, find the MGU of Y and g(a,a) given {Y/g(X,X)} :
Since Y is bound to g(X,X), find the MGU of g(a,a) and g(X,X) given {Y/g(X,X)}
to find the MGU of g(a,a) and g(X,X) given {Y/g(X,X)}

first, find the MGU of a and X given {Y/g(X,X)}, to get {Y/g(X,X), X/a}


next, find the MGU of a and X given {Y/g(X,X), X/a} to get {Y/g(X,X), X/a}
Thus, the MGU of f(g(X,X),Y) and f(Y,g(a,a)) given {} is {Y/g(X,X), X/a}

Resolution

To prove the preposition p(A,B) given the binding list {} using the fact p(a,b), all we need to do is find a MGU for p(A,B) and p(a,b) given {}, which is {A/a, B/b}.

To prove the preposition p(A,B) given the binding list {} using the rule p(w,X) :- q(X) and the fact q(z):
first, unify p(A,B) and p(w,X) using {} to get {A/w, B/X}
next, recursively prove q(X) using the binding list {A/w, B/X} to get {A/w, B/X, X/z}

To prove a list of prepositions { p(A,B), q(B,C) } given a binding list {}, first prove p(A,B) using {} to

get a new binding list L, then prove q(B,C) using L. For instance:
To prove { p(A,B), q(B,C) } given {}, using the facts p(w,x) and q(x,z):
prove p(A,B) given {} to get {A/w, B/x}
then prove q(B,C) given {A/w, B/x} to get {A/w, B/x, C/z}

To prove the preposition p(A,B) given the binding list {}, using the rule p(a,X) :- q(X), r(X) and the facts q(d) and r(d):


first, unitfy p(A,B) and p(a,X) to get the binding list {A/a, B/X}
next, recursively prove {q(X), r(X)} using the binding list {A/a, B/X} to get the binding list
{A/a, B/X, X/d }

Rule Renaming

Let's say you have the following fact in your database:

f(b,X).

This fact says that, f(b,X) is true for any value of X. Now, if you make the query:



?- f(X,a)

You are asking if there is any value of X such that f(X,a) is true. We'd like the system to come back with X = b, since the X in the query and the X in the rule are different X's – just as we can have different variables in different functions that have the same names. How can we allow different variables to have the same name? Before we use a rule to prove a query, we can rename all the variables. For instance, before trying to unify f(b,X) and f(X,a), we can rename the variables in the database fact to get f(X_1, a), then unify f(b, X) and f(X_1, a) to get {X_1/b, X/a}. Likewise, if you have the fact:

equal(X,X)
in your database, and you make the query equal(a,X), before trying to unify eqaul(a,X) and equal(X,X), rename the variables in the fact to get equal(X_1, X_1), and then unify equal(a,X) and equal(X_1, X_1) to get {X_1/a, X/X_1}.

Recursion & Backtracking

Once you know the rules and facts that need to be used to prove a query, you can use unification and resolution as described above. However, how do you know which facts and rules to use? Try them all! Let's say you have an array R of rules, a binding list L, and a set of P predicates to prove. How do you go about proving if the set of predicates (query terms) P can be solved given the binding list L and list of rules R?

To prove predicates P using array of rules R and binding list L:
for(i=0; i < NumRules;i++)

Rename the variables in R[i] to get RNew

Try to unify the consequent of RNew with the first element of L to get a new binding list L'

If you succeed, then recursively prove the list of terms consisting of the antecedent of RNew

concatenated with the rest of the predicates in P, to get L''

If you succeed, return the new binding list L''


Library Files

To help you out, and to keep this project from being too long, I have included some library files to assist with reading, writing, and renaming variables in terms and rules. The interface file for this library is the file term.h, reproduced below:


typedef struct _termlist *termlist; Datatypes – terms, rules, and lists of terms

typedef struct _term *term;

typedef struct _rule *rule;
struct _rule {

term consequent;

int numantecedent;

termlist antecedent;

};
struct _term {

int variable; /* Boolean value -- true (1) if term is a variable */

char * name;

int arity;

termlist args;

};
struct _termlist{

term first;

termlist rest;

};
/* Function printrule */

/* Purpose : Prints a rule to standard out in format */

/* consequent :- antecedent */

/* Input Param: rule r, the rule to print */

/* Output: the rule r */

void printrule(rule r);


/* Function printterm */

/* Purpose : Prints a term to standard out */

/* Input Param: term t, the term to print */

/* Output: the term t */

void printterm(term t);
/* Function getterm */

/* Purpose: Reads a term from the file f, skipping comments */

/* and whitespace. If an EOF is reached before a */

/* term is read, then the function returns NULL */

/* Input Param : The file f from which to read the terms */

/* Return value: The term that was read in */

term getterm(FILE *f);
/* Function gettermlist */

/* Purpose: Reads a list of terms (separated by commas) */

/* from the file f, skipping comments and whitespace */

/* If an EOF is reached before a term is read, */

/* then the function returns NULL */

/* Input Param : The file f from which to read the terms */

/* Return value: The list of terms that were read, or NULL */

termlist gettermlist(FILE *f);


/* Function getrule */

/* Purpose: Reads a rule from the file f, skipping comments */

/* and whitespace. If an EOF is reached before a */

/* rule is read, then the function returns NULL */

/* Input Param : The file f from which to read the terms */

/* Return value: The rule that was read in, or NULL */

rule getrule(FILE *f);
/* Function renamevars */

/* Purpose: Renames all of the variables in a rule. If the */

/* same variable appears twice in the rule, it will */

/* be given the same name each time it appears. */

/* The original rule is not modified in any way, */

/* the new rule is a duplicate made from newly */

/* malloc'd memory */

/* Input Param : The rule r for which we want to rename variables */

/* Return value: the rule r, with variables renamed. */

rule renamevars(rule r);


/* Function freerule */

/* Purpose: Frees all the heap memory used by rule r, including all */

/* subterms and strings in r. */

/* Input Param : The rule r which we wish to delete */

void freerule(rule r);
/* Function freeterm */

/* Purpose: Frees all the heap memory used by term t, including all */

/* subterms and strings in t. */

/* Input Param : The term t which we wish to delete */

void freeterm(term t);

Example Uses of Library Files

The file usingfunctions.c gives some examples of using these functions. The command 'make example' will compile this example code, and create the executable 'example' This code uses the library functions to read in all the rules from an input file, rename the variables in the rules, and then print the rules (with the variables renamed) out to standard out (the screen). Then, term lists are repeatedly read from standard input (the keyboard) and printed to the screen, until a halt term is read in


A (very sketchy) skeleton main program is also provided – the command 'make prolog' will compile this code and create the executable named prolog. All you need to do is modify this file to create a working prolog interpreter
The command 'make clean' removes all obect (.o) files and executables.

How to Proceed

This is a long project, and seems daunting at the start, but if you break it down into manageable chunks, then it is not so bad. Here is how I recommend attacking the problem:




  • First, write code to read in a list of rules. Test this code by writing out the rules after they have been read in!

  • Next, write a unify function. This function should take as input parameters two terms and a binding list, and it should return either a sentinel value or a new binding list, depending upon whether the unification was successful.

  • Next, test your unify function. Write a main that inputs two terms, tries to unify them, and then writes out the binding list

  • Next, write the prove function, following the pseudo-code above. After reading in the list of rules, your main should read in a list of terms, call unify to get a binding list, and either print out the binding list and yes, or no. Your binding lists can be quite long – it is OK at this point to have the query

    ?- plus(0,s(s(s(0))),Z) print out something like :


    Z_11 = X_14
    X_14 = 0
    Z_7 = s(Z_11)
    Y_11 = 0
    X_11 = 0
    Z_3 = s(Z_7)
    Y_7 = s(0)
    X_7 = 0
    Z = s(Z_3)
    Y_3 = s(s(0))
    X_3 = 0
    yes

    Note that the order of the variables in the binding list is not important. Your code does not need to produce exactly these variables in this order – this is just the general idea.





  • Next, write a function to compact the binding list, so that, for example, {X/Y, Y/Z, Z/a} would be reduced to {X/a, Y/a, Z/a}. Note that you only need to compact the binding list after you have finished proving the term, and right before the binding list is printed to the screen. So the query

    ?- plus(0,s(s(s(0))),Z) might print out something like :


    Z_11 = 0
    X_14 = 0
    Z_7 = s(0)
    Y_11 = 0
    X_11 = 0
    Z_3 = s(s(0))
    Y_7 = s(0)
    X_7 = 0
    Z = s(s(s(0))))
    Y_3 = s(s(0))
    X_3 = 0
    yes



  • Finally, modify your print function so that it only prints out bindings of variables that exist in the original query. Thus the query.

    ?- plus(0,s(s(s(0))),Z) should print out something like :


    Z = s(s(s(0)))
    yes

    If you wrote an exists function as part if doing unification, that will help here ...


Note that you can get partial credit for finishing some (but not all) of the above steps.




Testing your implementation

Once you finish your interpreter, you will need to test it. I have given you a Peano Arithmetic prolog file to help out. You also need to write some prolog functions of your own. Write the following prolog functions (which assume the existence of a parent(X,Y), male(X,Y), and female(X,Y) database) :

uncle(X,Y) true if X is the uncle of Y

married(X,Y) true if X and Y share a child (OK, this isn't the same as married in real life ...)


cousin(X,Y) true if X and Y are cousins. X and Y are cousins if they share a grandparent, or if X is
the descendant of a cousin of Y, or Y is the descendant of a cousin of X
greataunt(X,Y) true if X is the great aunt (mother of an aunt or uncle) of Y

You do not have to worry about negation, so it is OK for married(john,john) to be true if john has a child. A file family.pl, with parent, male, and female facts, is available online.



The Assignment

For this assignment, you need to :




  • Write a Prolog interpreter using the provided library files. A makefile, and skeleton main.c are provided for you. Your interpreter needs to work just like the standard gprolog, though you do not need to implement the "ask for more" feature. For maximal partial credit, attack the problem following the advice in the previous section.

  • Write prolog rules for uncle, married, cousin, and greataunt, as defined above. Add these rules to the provided family.pl file.



Extra Credit

You can receive up to 25 points of extra credit by modifying your interpreter to have the "ask for more answers" functionality. See me for pointers if you wish to try the extra credit.



What to turn in


  • A hardcopy of your source code is due by 5:00 p.m. on Wednesday, December 5th. You need to turn in a printout of main.c, as well as your uncle, married, cousin, and greataunt rules

  • In addition, copy all of the files necessary to compile and run your interpreter to your submit directory, under the subdirectory prolog. Make sure that all permissions are set to be group readable and group executable!

Download 69.23 Kb.

Share with your friends:




The database is protected by copyright ©ininet.org 2024
send message

    Main page