> val x = [1, 2, 3]; > val y = [1, 2, ?]; (* partial list *) > y := [1, 2, 3, ?]; (* increases info *) > y := [1, 5]; (* contradiction *)The last statement is not legal. This is because y has already been established as a "list" starting with 1 and 2, so the 5 is not legal. We cannot change our mind about the given facts.
The ? indicates the possibility of more information. The third line shows how the information can be increased.
Consider the append function:
- fun append (nil, x) = x | append (hd::tl, x) = hd::append(tl, x); - append([1, 2], [3, 4]); val it = [1, 2, 3, 4] : int listBut what if we want to find the result to the following query?
- append ([1, 2], x) = [1, 2, 3, 4]The desired result would be [3, 4] ; we want the system to fill in the missing information. ML won't know that we want to find the result of x in this case. We would have to write another function that dealt with this particular query.
- append relation(X, Y, Z): [] [1] [1] [1] [] [1] [1, 2] [3] [1, 2, 3] . . . . . .Where the relation is defined on an infinite set of tuples.
We could then ask a number of questions to see if the desired result exists:
append([], [], [])? yes append([], [1], [1])? yes append([1,2], [3], [1,2,3])? yes append([], [], [])? yesWhat if we ask the following?
append(x, [1], [1])? append([1], x, [1])? append([1], [], x)?In the first case, X is an output where Y and Z are inputs. In the second case, Y is an output where X and Z are inputs. In the third case, Z is the output while X and Y are inputs.
These all ask a similar question: is there an x such that the given pattern is in the relation append ? The output would be a possible result for x, if one existed. Subsequent queries would return other possible answers until all are exhausted.
With this type of language, it is necessary to provide both the relevant information and a method of inference for computing the results.
Arguments and results are treated uniformly. This means there is no distinction between input and output.
It is also important to note that Prolog is NOT a typed language.
Facts are also known as unconditional Horn Clauses. The general case of a Horn clause is:
P if Q1 and Q2 . . . and Qnwhere a fact is simply P with no conditional statements Q1, Q2 . . . . Note that horn clauses CANNOT represent negative information. That is, you cannot ask if a tuple is NOT in a relation.
Rules are also known as conditional Horn clauses.
Fact:
append([], X, X).This says that if you append [] and something, you will obtain something.
Rule:
append([a, b], [c, d], [a, b, c, d]) if append([b], [c, d], [b, c, d]).In Prolog, capital letters are variables and lower case letters are atoms. A "." (period) is used to end a statement. Another notation convention is described by: [a, b] = [a | [b]] where the | operator is equivalent to cons (i.e. it separates the head and tail of a list).
The above rule says: if the condition is met, return the appended result of the given lists.
Prolog:
append([], X, X). <----- fact append([H | X1], Y, [H | Z]) :- append(X1, Y, Z). <---- ruleThis is our database of knowledge. Now we can state goals to see if the information resides in the relation(s) created.
Note that the ":-" operator means "if" and tells us we are creating a rule. (Thus, P :- Q means P if Q .)
?- append([a, b], [c, d], [a, b, c, d]). yesThis goal was found in the relation.
?- append([a, b], [c, d], Z). Z = [a, b, c, d] ? yesWe query the system and it returns the first result it comes across in the relation that satisfies the value of Z.
The question mark after the result is the system asking if we want to search for more answers to our goal. Pressing enter means that we don't want to search for more answers. The system will respond that "yes" or "no", there are more answers that exist.
Similar to the above query:
?- append(X, [c, d], [a, b, c, d]). X = [a, b] ? yes ?- append([a, b], Y, [a, b, c, d]). Y = [c, d] ? yes
A fact and a rule:
append([], X, X). append([H | X], Y, [H | Z]) :- append(X, Y, Z).Goals:
| ?- append([a], [b, c, d], X). X = [a, b, c, d] ? ; <--------- ";" means show more answers no <--------- the system says there are no more answers | ?- append(X, Y, [a, b, c, d]). X = [], Y = [a, b, c, d] ? ; <--------- show more answers X = [a], Y = [b, c, d] ? ; X = [a, b], Y = [c, d] ? ; X = [a, b, c], Y = [d] ? ; X = [a, b, c d], Y = [] ? ; no <--------- the system says there are no more answers
append([], X, X). append([H | X], Y, [H | Z]) :- append(X, Y, Z). | ?- append(X, X, [b, c, b, c]). X = [b, c] ? ; noThis is pretty straight forward. But what happens when we ask this:
| ?- append([b, c], X, X). X = [b, c, b, c, b, c, b, c, b, c, b, c, b, c, b . . . .We get an infinite list.
For instance, the prefix relation is defined:
? prefix(X, Z) :- append(X, Y, Z).And the suffix relation can be defined:
? suffix(X, Z) :- append(Y, X, Z).
The Kowalski definition of Logic Programming is abstracted to:
ALGORITHM = LOGIC + CONTROLThe LOGIC is provided by the user/programmer and the CONTROL is derived by the system based on the LOGIC database of information.
In other words, we talk about programming in terms of deduction rather than evaluation.
P :- Q1, Q2, . . ., Qnwhere the goals Q1, Q2, . . . Qn are executed from left to right.
father(john, mary). father(sam, john). father(sam, kathy).Now, we start Prolog, load the file, and state some goals/queries:
sicstus <-------- command to start Prolog SICStus 2.1 *8: Wed Apr 28 18:33:10 PDT 1993 | ?- [facts]. <-------- load the file: facts | ?- father(john, mary). yesOur interpretation of the father relation father(X, Y) is that X is a father of Y .
So, when we query the system if john is the father of mary , the answer we get back is yes . Indeed, that fact is in our information database because of the file we loaded.
Now, we want to know if sam has any children:
| ?- father(sam, X). X = john ? ; <-------- are there any more? X = kathy ? ; noSam has two children: john and kathy .
Who is the father of mary?
| ?- father(X, mary). X = john ? ; no
| ?- father(X, john), father(X, kathy). X = sam ? ; no | ?- father(X, john), father(X, Y). X = sam, Y = john ? ; X = sam, Y = kathy ? ; noThe goals are stated in terms of two sub-goals which are "and"-ed together. The system sequentially searches the database of information from the top down. One pointer for each sub-goal traces through the database until matches that satisfy both sub-goals are reached.
In tracing the second example, we start out by trying to satisfy the first sub-goal father(X, john) . Starting at the top of the database, we look for a pattern that matches the sub-goal. The second item in the database, father(sam, john) matches. Thus, X = sam now.
Next, we try to satisfy the second sub-goal father(X, Y) which is actually father(sam, Y) now. We again begin tracing through the database looking for matches. The first match encountered is father(sam, john) . At this point, the entire goal has been satisfied and the result X = sam, Y = john is returned.
We enter a semi-colon which tells the system we want to look for another possible solution that satisfies our goal.
The system continues tracing through the file attempting to find a match to the goal father(sam, Y) . Another match is found at father(sam, kathy) . The goal has been satisfied again and the result X = sam, Y = kathy is returned.
When prompted for another possibility, the system continues searching for a match to the second sub-goal. There is no more information left in the database at this point. However, the pointer to the first sub-goal is still at father(sam, john) . We haven't exhausted all possibilities for the first sub-goal yet.
Therefore, we continue searching through the database looking for a match to the goal father(X, john) . None is found, so we are done. "No" is returned.
If another match had been found for the first sub-goal, we would look at the second sub-goal again starting from the beginning of the database.
| ?- father(X, john), father(X, Y), Y \== john. X = sam, Y = kathy ? ; noThe operator \== tells the system to ignore any matches where Y equals john . Thus, we eliminate the first solution we had in the previous example.
father(john, mary). father(sam, john). father(sam, kathy). | ?- father(X, Y), father(X, Z). X = john, Y = mary, Z = mary, ? ; X = sam, Y = john, Z = john ? ; X = sam, Y = john, Z = kathy ? ; X = sam, Y = kathy, Z = kathy ? ; no
<term> :- <term1>, <term2>, . . . . . , <termn> ^ ^ | | HEAD CONDITIONSThe head is the conclusion and the conditions are considered to be "and"-ed together. Note: A fact is just a special rule with no conditions. Example:
father(john, mary). <------ facts father(sam, john). father(sam, kathy). grandpa(X, Y) :- father(X, Z), father(Z, Y). <------ rule | ?- grandpa(X, Y). X = sam, Y = mary ? ; noWithout going into too much detail, the first condition of the rule will essentially match on anything in the given database of information. The important step is in the second condition as we try to satisfy the goal.
We essentially are looking for a match where one person is both a father and a child (variable Z) to two other people. These two other people are then returned as the grandpa and grandchild.
For each value of Z we encounter in the first condition we must then match that Z value to the other position in the father relation for the goal to be satisfied.
Unification is similar to pattern matching. However, there is a difference because pattern matching can only happen one way, from left to right. Unification can match both ways; it depends on where the variables and the atoms are.
| ?- X is 2+3. X = 5 ?X is instantiated to the value 5. The expression 2+3 is evaluated because we are using the operator "is" which tells us to evaluate 2+3 and make X that value.
To unify, we use the operator =.
| ?- X = 2 + 3. (2+3=X) + X = 2 + 3 ? / \ 2 3The diagram on the right is the term that X is unified with.
| ?- 5 = 2 + 3. noThis goal is NOT unifiable.
| ?- 2 + 3 = 2 + Y. + + Y = 3 ? / \ / \ 2 3 2 YThis goal is unifiable. We are increasing the information of Y to equal 3.
| ?- f(X, b) = f(a, Y). + + X = a, / \ / \ Y = b ? X b a YThis goal is unifiable because we can match X to a and Y to b .
| ?- 2*X = Y*(3+Y). * * X = 3 + 2, / \ / \ Y = 2 ? 2 X Y + / \ 3 YThe Y on the left is unified with the 2 on the right. The X on the left is the unified with (3 + Y) on the right which is actually (3 + 2) after the unification of Y with 2.
| ?- X = X + 2. X = Prolog interruption (h for help)? a {Execution aborted}What happened? The X on the left is infinitely matched to X + 2 on the right.
| ?- X = 2 + X X = 2+(2+(2+(2+(2+(2+(2+(2+(2+(2+(2+(2+(2+(2+( Prolog interruption (h for help)? a {Execution aborted}What happened? The same thing happened as above, except it knew what to do with each instance of 2+( that it found. The problem was that the unification for X still went on infinitely.
| ?- X is 2 + 3, X = 5. X = 5 ? | ?- X is 2 + 3, X = 2 + 3. no | ?- 2 + 3 is X. ERROR. | ?- X is 5, 6 is X + 1. X = 5 ? | ?- X = 5, 6 = X + 1. no | ?- 6 is X + 1, X = 5. ERROR. | ?- Y = 2, 2*X is Y*(Y+3). no | ?- Y = 2, Z is Y*(Y+3). Y = 2, Z = 10 ?
[a, b, c] = [a, b, c | []] = [a | [b, c]]This simply shows how lists are constructed with a head and a tail.
| ?- [H | T] = [a, b, c]. H = a, T = [b, c] ? | ?- [a | T] = [H, b, c]. H = a, T = [b, c] ?The head and tail is matched and the values are output.
- datatype bintree = empty | node of int * bintree * bintree; - fun member(k, empty) = false | member(k, node(n, s, t)) = if k < n then member(k, s) else if k < n then member(k, t) else true;How might you define a binary tree in Prolog?
member(K, node(K, _ , _ )). <------ fact member(K, node(N, S, _ )) :- K < N, member(K, S). <---- rules member(K, node(N, _ , T)) :- K > N, member(K, T).Note: the underscore "_" is used to "match" anything.
The fact defines the general structure of a tree -- a node with two branches. The first rule only worries about one side of the tree and searches that side of the tree if the value we are searching for is less than the current node.
Similarly, the second rule only worries about the other side of the tree and searches that side of the tree if the value we are searching for is greater that the current node.
This is a nice use of this language. It isn't even necessary to declare a special datatype to handle the tree; you simply write facts and rules to handle the data in the way you want to traverse the trees. The rest is handled by the system through deduction.
member(M, [M | _]). member(M, [_ | T]) :- member(M, T).The fact looks at the next item in the list to see if it exists. The rule checks to see if the item M is in the rest of the list, i.e. T.
What does the following return?
| ?- member(a, [a]). yesThis matches to the fact. What about this:
| ?- member(a, [b]) noThis fails at the fact because the first item in the list is b not a . The condition in the rule also fails because the tail of the list [b] is just b which won't satisfy the fact. Thus, there is no match in the relation.
overlap(X,Y) :- member(M, X), member(M, Y). | ?- overlap(Z, [a, b, c, d]), member(Z, [1, 2, c, d]). (infinite computation) | ?- X = [1, 2, 3], member(a, X). no | ?- member(a, X), X = [1, 2, 3]. (infinite computation)The most interesting thing to note here is the difference between the answers of the second and third queries.
The second query simply answers "no", but the third query never returns. Why does this happen? The best way to examine what happens is to look at the associated relation tree which shows the structure of the possible solutions:
member(a,X) / \ one ------> X=[a|_] X=[_|T] <------ another possibility ?member(a,T) possibility / \ T=[a|_] T=[_|T'] X=[_,a,_] ?member(a,T') / \ T'=[a|_] T'=[_|T''] X=[_,_,a,_] ?member(a,T''')When we know X , as in the second query, we can compare a to each element in the list. No matches occur, so "no" is returned.
When we don't know X , as in the third query, the system keeps looking for elements in X to compare to a . . .it never finds any, but it keeps searching indefinitely.
In essence, it constructs the equivalent of a search tree and traverses it until if finds something that satisfies the goal.
Notice how the order of the expressions in the goal made a difference in how the result was deduced.
Prolog uses depth-first "traversal" because each sub-goal must be satisfied before any subsequent sub-goals are satisfied. The ideal method would use breadth-first "traversal" where each sub-goal is examined in parallel.
However, breadth-first requires a large amount of memory and resources. This is why depth-first was used in Prolog. Unfortunately, this method does have its drawbacks as demonstrated by the following:
f(X) :- f(X). ?f(1) f(1). / \ f(1) yes <------ two | ?- f(1) / \ possibilities (infinite computation) f(1) yes / \ . .You might expect this to simply return f(1). In fact, if this were true logic, it SHOULD return f(1). However, because Prolog is implemented in a depth-first manner we never get to the second possibility on any level; we just keep traversing the rightmost branches. Thus, it traverses the tree forever.
| ?- L = [a, b | X]. L = [a, b | X] ? ; no | ?- L = [a, b | X], X = [C, Y]. L = [a, b, C, Y], X = [C, Y] ? ; noNotice how the information about L is increased by unifying X to the list [C, Y] .
Unification of a variable representing the end of the list is similar to an assignment to that variable.
Considering the possibility tree, backtracking refers to retracing you steps as you follow branches back up to previously untraversed branches.
A similar example to the example in the previous section:
f(x) :- fail ?f(1) f(1) / \ fail yesSteps followed: