Prolog Sli
Prolog Sli
Was picked by Japan in 1981 as a core technology for their "fifth generation" project.
There are many free implementations of Prolog available. We'll be using SWI-Prolog.
% cat foods.pl
food('apple').
food('broccoli').
food('carrot').
food('lettuce').
food('rice').
These facts enumerate some things that are food. We might read them in English like this:
"An apple is food", "Broccoli is food", etc.
A fact represents a piece of knowledge that the Prolog programmer deems to be useful. The
name food was chosen by the programmer. One alternative is edible('apple').
'apple' is an example of an atom. Note the use of single quotes, not double quotes. We'll
learn more about atoms later.
% pl (That's "PL")
Welcome to SWI-Prolog (Multi-threaded, Version 5.6.20)
...
?- food('peach').
No
Prolog responds based on the facts it has been given. People know that peaches are food but
Prolog doesn't know that because there's no fact that says that.
A query can consist of one or more goals. The queries above consist of one goal.
food('apple').
Here's a query:
food('apple').
Facts and queries have the same syntax. They are distinguished by the context in which they
appear.
When a file is loaded with [filename], its contents are interpreted as a collection of facts.
We'll see later that files can contain "rules", too. Facts and rules are two types of clauses.
For the time being use all-lowercase filenames with the suffix .pl for Prolog source files.
- food "apple";
val it = true : bool
- food "peach";
val it = false : bool
?- food(Edible).
Edible = apple <cursor is here>
The above query asks, "Tell me something that you know is a food."
Prolog uses the first food fact and responds with Edible = apple, using the variable
name specified in the query.
If the user is satisfied with the answer apple, pressing <ENTER> terminates the query.
Prolog responds with "Yes" because the query was satisfied.
?- food(Edible).
Edible = apple <ENTER>
Yes
?-
?- food(Edible).
Edible = apple ;
Edible = broccoli ;
Edible = carrot ;
Edible = lettuce ;
Edible = rice ;
No
In the above case the user exhausts all the facts by repeatedly responding with a semicolon.
Prolog then responds with "No".
It is very important to recognize that a simple set of facts lets us perform two distinct
computations: (1) We can ask if something is a food. (2) We can ask what all the foods are.
How could we construct an analog for the above behavior in Java, ML, or Ruby?
The query
?- food('apple').
can be thought of as asking, "Can you prove that apple is a food?" It is trivially proven
because we've supplied a fact that says that apple is a food.
The query
?- food('pickle').
produces "No" because based on the facts we've supplied, Prolog can't prove that pickle is a
food.
The query asks, "For what values of F can you prove that F is a food? By repeatedly entering
a semicolon we see the full set of values for which that can be proven.
The collection of knowledge at hand, a set of facts about what is food, is trivial but Prolog is
capable of finding proofs for an arbitrarily complicated body of knowledge.
One way to specify an atom is to enclose a sequence of characters in single quotes. Here are
some examples:
An atom can also be specified by a sequence of letters, digits, and underscores that begins
with a lowercase letter. Examples:
Is it common practice to avoid quotes and use atoms that start with a lowercase letter:
food(apple).
food(broccoli).
...
?- atom('apple').
Yes
?- atom(apple).
Yes
?- atom("apple").
No
?- number(10).
Yes
?- number(123.456).
Yes
?- number('100').
No
?- 3 + 4.
ERROR: Undefined procedure: (+)/2
?- a = 5.
No
?- Y = 4 + 5.
Y = 4+5
Yes
color(sky, blue).
color(grass, green).
number(one, 1, 'English').
number(uno, 1, 'Spanish').
number(dos, 2, 'Spanish').
We can say that the facts above define three predicates: color/2, odd/1, and number/3.
The number following the slash is the number of terms in the predicate.
Structures consist of a functor (always an atom) followed by one or more terms enclosed in
parentheses.
color(grass, green)
odd(1)
number(uno, 1, 'Spanish')
equal(V, V)
The structure functors are color, odd, number, equal, and lunch, respectively.
Note that a structure can be interpreted as a fact or a goal, depending on the context.
x(just(testing,date(7,4,1776))).
x(10).
?- x(abc).
No
?- x([1,2,3]). % A list!
No
?- x(a(b)).
No
The predicate consists of two facts, one with a term that's a structure and another that's an
integer. That inconsistency is not considered to be an error. The goals in the queries have
terms that are an atom, a list, and a structure. There's no indication that those queries are
fundamentally mismatched with respect to the terms in the facts.
Prolog says "No" in each case because nothing it knows about aligns with anything it's being
queried about.
x(just(testing,date(7,4,1776))).
x(10).
?- x(1,2).
ERROR: Undefined procedure: x/2
ERROR: However, there are definitions for:
ERROR: x/1
?- color(Thing, Color).
Thing = sky
Color = blue ;
Thing = dirt
Color = brown ;
Thing = grass
Color = green ;
...
We're essentially asking this: For what pairs of Thing and Color can you prove color?
Note that the ordering of the goals was reversed. In this case the order doesn't matter.
likes(bob, carrot).
likes(bob, apple).
Who likes a food? likes(joe, lettuce).
likes(mary, broccoli).
likes(mary, tomato).
likes(bob, mary).
Who likes green foods? likes(mary, joe).
likes(joe, baseball).
likes(mary, baseball).
likes(jim, baseball).
Who likes foods with the same color as foods that
Mary likes?
Answers:
likes(Who, baseball).
likes(Who, X), food(X).
likes(Who, X), food(X), color(X,green).
likes(mary,F), food(F), color(F,C), likes(Who,F2), food(F2), color(F2,C).
?- food(F1),food(F2),color(F1,C),color(F2,C).
F1 = apple
F2 = apple
C = red ;
F1 = broccoli
F2 = broccoli
C = green ;
To avoid foods matching themselves we can specify "not equal" with \==.
Remember that in order for a query to produce an answer for the user, all goals must succeed.
?- \==(abc,xyz).
Yes
In fact, the sequence abc \== xyz causes Prolog to create a structure. display/1 can
be used to show the structure:
Ultimately, abc \== xyz means "invoke the predicate named \== and pass it two terms,
abc and xyz".
Nested sidebar: help/1 displays the documentation for a predicate. To learn about the op
predicate, do this:
?- help(op).
What is a food?
?- thing(X, _, yes).
X = apple ;
X = broccoli ;
X = carrot ;
...
The underscore designates an anonymous logical variable. It indicates that any value
matches and that we don't want to have the value associated with a variable (and displayed).
Answers:
thing(lettuce, Color, _).
thing(X, green, no).
thing(F, orange, yes).
thing(lettuce, Color, _), thing(Food, Color, yes).
The operators == and \== test for equality and inequality. They are roughly analogous
to = / <> in ML and == / != in Ruby:
?- abc == 'abc'.
Yes
?- 3 \== 5.
Yes
?- abc(xyz) == abc(xyz).
Yes
?- abc(xyz) == abc(xyz,123).
No
Just like comparing tuples and lists in ML, and arrays in Ruby, structure comparisons in
Prolog are "deep". Two structures are equal if they have the same functor, the same number
of terms, and the terms are equal. Later we'll see that deep comparison is used with lists, too.
Think of == and \== as asking a question: is one thing equal (or not equal) to another.
If both operands are variables then A = B specifies that A must have the same value as B.
Examples:
?- A = 1, B = abc, A = B.
No
?- A = 1, B = 1, A = B.
A = 1
B = 1 <CR>
Yes
?- A = B, B = 1.
A = 1
B = 1
There are two unifications. The first unification demands that A must equal B. The second
unification demands that B must equal 1. In order to satisfy those two demands, Prolog says
that A must be 1.
?- A = B, B = 1.
A = 1
B = 1
Variables are initially uninstantiated. After A = B we can say that A is unified with B but
both A and B are still uninstantiated.
Another way to think about it is that unifications create constraints that Prolog must honor in
order for a query to succeed. If the constraints can't be honored, the query fails.
?- A = 1, A = 2.
No
?- B = C, A = B, B = 1, C = 2.
No
?- S = f(A), A = abc.
S = f(abc)
A = abc
Yes
Remember that a query specifies a series of goals. The above goals can be placed in any
order. The result is the same regardless of the order.
?- food(carrot).
Here's a way to picture how Prolog considers the first fact, which is food(apple).
The demands of the three unifications cannot be satisfied simultaneously . The same is true
for the second fact, food(broccoli).
The instantiations for Fact and Query are shown, but are no surprise.
The query succeeds and Prolog shows that F has been instantiated to apple.
?- food(F).
F = apple ;
F = broccoli ;
F = carrot ;
F = lettuce ;
F = rice ;
No
Prolog first finds that food(apple) can be unified with food(F) and shows that F is
instantiated to apple.
When the user types semicolon F is uninstantiated and the search for another fact to unify
with food(F) resumes.
The process continues until Prolog has found all the facts that can be unified with food(F)
or the user is presented with a value for F that is satisfactory.
C = green
F = lettuce
...
What we see is that unifying Fact1 with Query1 causes F to be instantiated to lettuce.
In the Active Prolog Tutor, Dennis Merritt describes the ports in this way:
call: Using the current variable bindings, begin to search for the facts which unify with
the goal.
exit: Set a place marker at the fact which satisfied the goal. Update the variable table to
reflect any new variable bindings. Pass control to the right.
redo: Undo the updates to the variable table [that were made by this goal]. At the place
marker, resume the search for a clause which unifies with the goal.
?- food(X).
X = apple ;
X = broccoli ;
X = carrot ;
X = lettuce ;
X = rice ;
No
?- trace, food(X).
Call: food(_G410) ? <CR>
Exit: food(apple) ? <CR>
X = apple ;
Redo: food(_G410) ? <CR>
Exit: food(broccoli) ? <CR>
X = broccoli ;
Redo: food(_G410) ? <CR>
Exit: food(carrot) ? <CR>
X = carrot ;
Tracing shows the transitions through each port. The first transition is a call on the goal
food(X). The value shown, _G410, stands for the uninstantiated variable X. We next see
that goal being exited, with X instantiated to apple. The user isn't satisfied with the value
and by typing a semicolon forces the redo port to be entered, which causes X, previously
bound to apple, to be uninstantiated. The next food fact, food(broccoli) is tried,
instantiating X to broccoli, exiting the goal, and presenting X = broccoli to the user.
(etc.)
Facts:
?- writeln('apple'), writeln('pie').
apple
pie
Yes
?- writeln('apple\npie').
apple
pie
Yes
Yes
Yes
If more than one value is to be output, the values must be in a list. We'll see more on lists
later but for now note that we make a list by enclosing zero or more terms in square brackets.
Lists are heterogeneous, like Ruby arrays
~w is one of many format specifiers. The "w" indicates to use write/1 to output the value.
F = apple ;
broccoli is a food
F = broccoli ;
carrot is a food
Any ideas?
In essence, this query is a loop. food(F) unifies with the first food fact and instantiates F
to its term, the atom apple. Then format is called, printing a string with the value of F
interpolated. food('none') unifies with nothing, and fails. Control then moves left, into
the redo port of format. format doesn't erase the output but it doesn't have an
alternatives either, so it fails, causing the redo port of food(F) to be entered. F is
uninstantiated and food(F) is unified with the next food fact in turn, instantiating F to
broccoli. The process continues, with control repeatedly moving back and forth until all
the food facts have been tried.
We might say, "The query gets a food F, prints it, fails, and then backtracks to try the next
food."
By design, Prolog does not analyze things far enough to recognize that it will never be able to
"prove" what we're asking. Instead it goes through the motions of trying to prove it and as
side-effect, we get the output we want. This is a key idiom of Prolog programming.
It's important to note that different predicates respond to "redo" in various ways. With only a
collection of facts for food/1, redo amounts to advancing to the next fact, if any. If there is
one, the goal exits (goes to the right). If not, it fails (goes to the left).
A sequence of redos might cause a predicate to work through a series of URLs to find a
current data source.
The predicate fail/1 always fails. It's important to understand that an always-failing goal
like food('none') or 1==2 produces exhaustive backtracking but in practice we'd use
fail instead:
No
In terms of the four-port model, think of fail as a box whose call port is "wired" to its fail
port:
Another way to think about it: fail/1 causes control to make a U-turn.
?- between(1,3,X).
X = 1 ;
X = 2 ;
X = 3 ;
No
Horn clauses
Rules with arguments
Instantiation as "return"
Computing with facts
Sidebar: Describing predicates
Arithmetic
Comparisons
Facts are one type of Prolog clause. The other type of clause is a rule. Here is a rule:
Just like facts, rules are not entered at the query prompt (it would be interpreted as a query!).
Instead, they put into a file along with facts. The rules are loaded by consulting the file.
% cat facts1.pl
showfoods :- food(F), format('~w is a food\n', F), fail.
food(apple).
food(broccoli).
...
% pl
Welcome to SWI-Prolog (Multi-threaded, Version 5.6.12)
?- [facts1].
...
The order doesn't matter—even though showfoods/0 uses food/1, it can precede it.
"facts1.pl" is now a misnomer because it now contains rules, too. We'll ignore that.
If Q1, Q2, Q3, ..., Qn, are all true, then P is true.
The query
?- p.
which asks Prolog to "prove" p, causes Prolog to try and prove q1, then q2, and then q3. If
it can prove all three, and can therefore prove p, Prolog will respond with Yes. (If not, then
No.)
Note that this is an abstract example—we haven't defined the predicates q1/0 et al.
?- showfoods.
apple is a food
broccoli is a food
carrot is a food
lettuce is a food
rice is a food
No
In its unsuccessful attempt to "prove" showfoods, and thus trying to prove all three goals
in the body of the showfoods rule, Prolog ends up doing what we want: all the foods are
printed.
In other words, we send Prolog on a wild goose chase to get our work done!
No
What's wrong?
At hand:
No
The showfoods rule above ALWAYS fails—there's NO WAY to get past the fail/0 at
the end. We get the output we want but because the first showfoods goal ultimately fails
(when the food facts are exhausted) Prolog doesn't try the second two goals—it can't even get
past the first goal!
You might have noticed that Prolog concludes with No after printing the foods. That's
because it fails to prove showfoods.
We've seen Prolog try all facts in turn for predicates like food/1, and color/2 in order to
satisfy a query. Let's add a second clause to the predicate showfoods/0. The second
clause is a fact:
Result:
?- showfoods.
apple is a food
broccoli is a food
carrot is a food
lettuce is a food
rice is a food
Prolog tried the two clauses for the predicate showfoods/0 in turn. The first clause, a
rule, was ultimately a failure but printed the foods as a side-effect. Because the first clause
failed, Prolog tried the second clause, a fact which is trivially proven.
?- gtrace, showfoods.
apple is a food
and a few clicks on the right-arrow button, produces the output above and the display below.
?- foodWithColor(green).
Yes
To prove the goal foodWithColor(green), Prolog first searches its clauses for one that
can be unified with the goal. It finds a rule (above) whose head can be unified with the goal.
Then the variable Color in the clause is instantiated to the atom green. It then attempts to
prove food(F), and color(F, green) for some value of F. The Yes response tells us
that at least one green food exists, but that's all we know.
A failure:
?- foodWithColor(blue).
No
Ignoring the facts that you know are present, what are two distinct possible causes for the
failure?
Can we do anything with it other than asking if there is a food with a particular color?
?- foodWithColor(C).
C = red ;
C = green ;
...
When a variable is supplied in a query and it matches a fact or the head of a rule with a
variable in the corresponding term, the two variables are unified. (Instantiating one
instantiates the other.)
In the above case the variable C first has the value red because C in the query was unified
with Color in the head of the rule, AND the goals in the body of the rule succeeded, AND
Color was instantiated to red.
When we type a semicolon in response to C = red, Prolog backtracks and then comes up
with another food color, green.
Prolog has no analog for "return x" as is found in most languages. In Prolog there is no
way to say something like this,
or this,
?- writeln(foodWithColor()), fail.
get_fwc(Color, Result) :-
food(F), color(F,Color), Result = fwc(F,Color).
Usage:
?- get_fwc(green, R).
R = fwc(broccoli, green) ;
R = fwc(lettuce, green) ;
No
?- get_fwc(_, R).
R = fwc(apple, red) ;
R = fwc(broccoli, green) ;
R = fwc(carrot, orange) ;
R = fwc(lettuce, green) ;
R = fwc(rice, white) ;
No
get_fwc(Color, Result) :-
food(F), color(F,Color), Result = fwc(F,Color).
?- get_fwc(green,R).
R = fwc(broccoli, green)
Instead of unifying Result with a structure in the body of the rule, we can eliminate the
"middle-man" Result and specify the structure in the head, unifying it with whatever is
specified in the query:
equal_e(X,Y) :- X == Y. % equality
equal_u(X,Y) :- X = Y. % unification
How does their behavior differ? What's something we could do with one that we couldn't do
with the other?
equal_u(X,Y) :- X = Y. % unification
Usage:
?- equal_e(abc,abc).
Yes
?- equal_e(abc,X).
No
?- equal_u(abc,X).
X = abc
?- equal_u(X,abc).
X = abc
equal_u(X,Y) :- X = Y.
equal_u(X,X).
Below are some more facts that perform computation. Describe what they do.
square(rectangle(S, S)).
Usage:
?- square(rectangle(3,3)).
Yes
?- rotate(rectangle(3,5),R2).
R2 = rectangle(5, 3)
Yes
?- rotate(muffler,M2).
No
One way to think about a square is that it's a rectangle whose 90-degree rotation equals itself:
square(R) :- rotate(R,R).
The thing to appreciate is that rotate describes a relationship between two rectangle
structures. With a single fact Prolog can create a rotated rectangle, determine if one rectangle
is a rotation of another, and more.
If an argument has a plus prefix, like +Low and +High, it means that the argument is an
input to the predicate. A question mark indicates that the argument can be input or output.
The documentation indicates that between can (1) generate values and (2) test for
membership in a range.
?- between(1,10,X).
X = 1
...
?- between(1,10,5).
Yes
rotate(Rect1, Rect2)
square(Rectangle)
equal_u(X, Y)
format(+Format, +Arguments)
?- display(1 + 2 * 3).
+(1, *(2, 3))
Yes
?- display(1 / 2 * ( 3 + 4)).
*(/(1, 2), +(3, 4))
Yes
?- display(300.0/X*(3+A*0.7**Y)).
*(/(300.0, _G373), +(3, *(_G382, **(0.7, _G380))))
Unlike \==, there are no predicates for the arithmetic operators. Example:
?- +(3,4).
ERROR: Undefined procedure: (+)/2
?- *(10,20).
ERROR: Undefined procedure: * /2
?- is(X, 3+4*5).
X = 23
Yes
?- X is 3 + 4, Y is 7 * 5, Z is X / Y.
X = 7
Y = 35
Z = 0.2
?- A is 3 + X.
ERROR: is/2: Arguments are not sufficiently instantiated
?- write(3+4).
3+4
Yes
?- 3+4 == 7.
No
-X negation
X+Y addition
X-Y subtraction
X*Y multiplication
X/Y division—produces float quotient
X//Y integer division
X mod Y integer remainder (Watch out for X is Y % Z—that's a comment!)
integer(X) truncation to integer
float(X) conversion to float
sign(X) sign of X: -1, 0, or 1
?- X is 43243432422342123234 / 77777777777777777.
X = 555.987
?- X is 10 // 3.
X = 3
?- X is e ** sin(pi).
X = 1.0
area(rectangle(W,H), A) :- A is W * H.
area(circle(R), A) :- A is pi * R ** 2.
?- add(3,4,X).
X = 7
?- around(P, 7, N).
P = 6
N = 8
?- area(circle(3), A).
A = 28.2743
?- 3 + 5 =:= 2*3+2.
Yes
?- X is 3 / 5, X > X*X.
X = 0.6
Note that the comparisons produce no value; they simply succeed or fail.
print_grade(Score) :- grade(Score,Grade),
format('~w -> ~w\n', [Score, Grade]).
Usage:
?- grade(95,G).
G = 'A'
Yes
?- print_grade(80).
80 -> B
Yes
?- print_grade(55).
55 -> F
Yes
male(tom). parent(tom,betty).
male(jim). parent(tom,bob).
male(bob). parent(jane,betty).
male(mike). parent(jane,bob).
male(david). parent(jim,mike).
parent(jim,david).
female(jane). parent(betty,mike).
female(betty). parent(betty,david).
female(mary). parent(bob,alice).
female(alice). parent(mary,alice).
Queries:
Is Mike a sibling of Alice?
If a predicate contains a goal that refers to itself the predicate is said to be recursive.
?- ancestor(a,f). % Is a an ancestor of f?
Yes
?- ancestor(c,b). % Is c an ancestor of b?
No
printN(0).
printN(N) :- N > 0, M is N - 1, printN(M), writeln(N).
Usage:
?- printN(3).
1
2
3
Yes
Note that we're asking if printN(3)can be proven. The side effect of Prolog proving it is
that the numbers 1, 2, and 3 are printed.
Is printN(0). needed?
sumN(0,0).
sumN(N,Sum) :-
N > 0, M is N - 1, sumN(M, Temp), Sum is Temp + N.
Usage:
?- sumN(4,X).
X = 10
Yes
Note that this predicate can't be used to determine N for a given sum:
?- sumN(N, 10).
ERROR: >/2: Arguments are not sufficiently instantiated
sumN(0,0).
sumN(N,Sum) :-
N > 0, M is N - 1, sumN(M, Temp), Sum is Temp + N.
sumN(0,0).
sumN(N,Sum) :-
N > 0, M is N - 1, sumN(M, Sum), Sum is Sum + N.
Remember that is/2 unifies its left operand with the result of arithmetically evaluating it's
right operand. Further remember that unification is neither assignment or comparison.
factorial(0,1).
factorial(N,F) :-
N>0,
N1 is N-1,
factorial(N1,F1),
F is N * F1.
Near the bottom the page has an excellent animation of the computation
factorial(3,X). TRY IT!
An alternative:
odd(1).
odd(N) :- odd(M), N is M + 2.
odd(1).
odd(N) :- odd(M), N is M + 2.
Usage:
?- odd(5).
Yes
?- odd(X).
X = 1 ;
X = 3 ;
X = 5 ;
...
odd(1).
odd(N) :- odd(M), N is M + 2.
odd(1).
odd(N) :- odd(M), N is M + 2.
odd(1).
odd(N) :- odd(M), N is M + 2.
odd(1).
odd(N) :- odd(M), N is M + 2.
odd(1).
odd(N) :- odd(M), N is M + 2.
With generative predicates like this one a key point to understand is that if an
alternative is requested, another activation of the predicate is created.
As a contrast, think about how execution differs with this set of clauses:
odd(1).
odd(3).
odd(N) :- odd(M), N is M + 2.
[1, 2, 3]
Common mistake: Entering a list literal as a query is taken as a request to consult files.
?- [abc, 123].
ERROR: source_sink `abc' does not exist
One way to "see" a list is to write it; another way is to unify it with a variable:
?- write([1,2,3,4]).
[1, 2, 3, 4]
?- X = [a,1,b,2].
X = [a, 1, b, 2]
?- [1,2,3] = [X,Y,Z].
X = 1
Y = 2
Z = 3
?- [X,Y] = [1,[2,[3,4]]].
X = 1
Y = [2, [3, 4]]
?- [X,Y] = [1].
No
?- Z = [X,Y,X], X = 1, Y = [2,3].
Z = [1, [2, 3], 1]
X = 1
Y = [2, 3]
Write a predicate empty(L) that succeeds iff L is an empty list. BE SURE it succeeds only
on lists and no other types.
Write a predicate f(X) that succeeds iff X is a list with one or three elements or X is an odd
number.
?- [A,B,C] = [C,C,A].
A = _G296
B = _G296
C = _G296
empty([]).
f([_]).
f([_,_,_]).
f(N) :- number(N), N mod 2 =:= 1.
length(?List, ?Len) can be used to get the length of a list OR make a list of
variables:
?- length([10,20,30],Len).
Len = 3
?- length(L,3).
L = [_G319, _G322, _G325]
?- reverse([1,2,3], R).
R = [3, 2, 1]
?- reverse([1,2,3], [1,2,3]).
No
?- numlist(5,10,L).
L = [5, 6, 7, 8, 9, 10]
sumlist(+List, -Sum) unifies Sum with the sum of the values in List, which must
all be numbers or structures that can be evaluated with is/2.
?- numlist(1,5,L), sumlist(L,Sum).
L = [1, 2, 3, 4, 5]
Sum = 15
?- atom_chars('abc',L).
L = [a, b, c]
?- msort([3,1,7], L).
L = [1, 3, 7]
member(?Elem, ?List) succeeds when Elem can be unified with a member of List.
Problem: Write a predicate has_vowel(+Atom) that succeeds iff Atom has a vowel.
?- has_vowel('ack').
Yes
?- has_vowel('pfft').
No
Solution:
has_vowel(Atom) :-
atom_chars(Atom,Chars),
member(Char,Chars),
member(Char,[a,e,i,o,u]).
?- append([1,2],[3,4,5],R).
R = [1, 2, 3, 4, 5]
append is not limited to concatenation. Note what happens when we supply List3, but
not List1 and List2:
L1 = [1]
L2 = [2, 3] ;
L1 = [1, 2]
L2 = [3] ;
L1 = [1, 2, 3]
L2 = [] ;
No
At hand:
L1 = [1]
L2 = [2, 3] ;
...
Think of append as demanding a relationship between the three lists: List3 must consist
of the elements of List1 followed by the elements of List2. If List1 and List2 are
instantiated, List3 must be their concatenation. If only List3 is instantiated then List1
and List2 represent (in turn) all possible the ways to split List3.
Here are some more computations that can be expressed with append:
drop(L, N, Result) :-
append(Dropped, Result, L), length(Dropped, N).
?- append(A,B,X), append(X,C,[1,2,3]).
chunk(L,N,Chunk) :-
length(Chunk,N),
append(Chunk,_,L).
chunk(L,N,Chunk) :-
length(Junk, N),
append(Junk,Rest,L),
chunk(Rest,N,Chunk).
Usage:
?- chunk([1,2,3,4,5],2,Chunk).
Chunk = [1, 2] ;
Chunk = [3, 4] ;
No
findall can be used to create a list of values that satisfy a goal. Here is a simple example:
Foods is in alphabetical order because the food facts happen to be in alphabetical order.
The first argument of findall is a template. It is not limited to being a single variable. It
might be a structure. The second argument can be a series of goals joined with conjunction.
Remember that -/2 is a functor that can be placed between its terms. findall(-
(F,C),... produces the same result as the above.
The following query creates a list structures representing foods and the lengths of their
names.
Two predicates that are related to findall are bagof/3 and setof/3 but they differ
more than may be grasped at first glance. Section 6.3 in the text has a discussion of them; if
they're needed in an assignment we'll discuss them more.
keysort(+List, -Sorted) sorts a list of structures with the functor '-' based on
their first term:
[1 | [2, 3]]
More generally, a list can be described as a sequence of initial elements and a tail.
[1 | [2,3,4]]
[1,2 | [3,4]]
[1,2,3 | [4]]
[1,2,3,4 | []]
?- [H|T] = [1,2,3,4].
H = 1
T = [2, 3, 4]
?- A = [1], B = [A|A].
head(L, H) :- L = [H|_].
Usage:
?- head([1,2,3],H).
H = 1
?- head([], X).
No
member(X,L) :- L = [X|_].
member(X,L) :- L = [_|T], member(X, T).
Usage:
?- member(1, [2,1,4,5]).
Yes
?- member(a, [2,1,4,5]).
No
We've seen that member can be used to generate the elements in a list:
?- member(X, [a,b,c]).
X = a ;
X = b ;
...
member(X,[X|_]).
X is a member of the list having X as its head
member(X,[_|T]) :- member(X,T).
X is a member of the list having T as its tail if X is a member of T
Problem: Define a predicate last(L,X) that describes the relationship between a list L and
its last element, X.
?- last([a,b,c], X).
X = c
Yes
?- last([], X).
No
lastx([X],X).
lastx([_|T],X) :- last(T,X).
Problem: Write a predicate len/2 that behaves like the built-in length/2
?- len([], N).
N = 0
?- len([a,b,c,d], N).
N = 4
?- len([a,b,c,d], 5).
No
?- len(L,1).
L = [_G295]
len([], 0).
len([_|T],Len) :- len(T,TLen), Len is TLen + 1.
Problem: Define a predicate allsame(L) that describes lists in which all elements have the
same value.
?- allsame([a,a,a]).
Yes
?- allsame([a,b,a]).
No
?- allsame([A,B,C]), B = 1.
A = 1
B = 1
C = 1
?- allsame(L).
L = [_G305] ;
L = [_G305, _G305] ;
?- p(X).
X = [] ;
X = [_G272] ;
X = [_G272, _G272] ;
X = [_G272, _G272, _G278] ;
X = [_G272, _G272, _G278, _G278] ;
X = [_G272, _G272, _G278, _G278, _G284] ;
append([], X, X).
append([X|L1], L2, [X|L3]) :- append(L1, L2, L3).
Try tracing it. To avoid getting the built-in version, define the above as my_append instead
of append (three places). Then try these:
?- gtrace, my_append([1,2,3,4],[a,b,c,d],X).
?- gtrace, my_append([a,b,c,d,e,f,g],[],X).
?- display([1,2,3]).
.(1, .(2, .(3, [])))
?- X = .(a, .(b,[])).
X = [a, b]
member(X, .(X,_)).
member(X, .(_,T)) :- member(X,T).
?- X = .(1,1).
?- food(computer).
No
?- \+food(computer).
Yes
?- listing(inedible).
inedible(A) :- \+food(A).
?- inedible(computer).
Yes
?- inedible(banana).
Yes
?- inedible(X).
No
Any surprises?
Backtracking can be limited with the "cut" operator, represented by an exclamation mark.
No
No
One way to picture a cut is like a one-way gate: control can go through it from left to right,
but not from right to left.
Usage:
?- ce1(one,X).
X = 1 ;
X = something ;
No
?- ce1(two, X).
X = 2 ;
No
Note that the third clause is not tried for ce1(two,X). That's due to the cut.
In a rule, a cut still acts as a one-way gate in the rule itself but it also prevents
consideration of subsequent clauses for the current call of that predicate. It's "do-or-die"
(succeed or fail) with the rule at hand.
x :- a, b, c, !, d, e, f, !, g, h, i.
and a query:
?- x, y.
Control may circulate between a, b, and c but once c is proven (and the cut passed through),
a, b, and c will be not be considered again during a particular call of x. Similarly, once f
succeeds, we are further committed.
However, if x succeeds and y fails, control will backtrack into g, h, and i if they contain
unexplored alternatives.
?- grade2(85,G).
G = 'B' ;
G = 'C' ;
G = 'F' ;
No
Usage:
?- do_grades([bob-87, mary-92]).
bob: B
bob: C
bob: F
mary: A
mary: B
mary: C
mary: F
Solution:
Usage: do_grades(Students) :-
member(Who-Avg, Students),
?- grade3(85,G). grade3(Avg,Grade),
G = 'B' ; format('~w: ~w~n', [Who, Grade]), fail.
No
?- do_grades([bob-87, mary-92]).
bob: B
mary: A
Problem: Write a predicate agf(-F) that finds at most one green food.
max(X,Y,X) :- X >= Y, !.
max(_,Y,Y).
xmember(X, [X|_]) :- !.
xmember(X, [_|T]) :- xmember(X,T).
How does its behavior differ from the standard version? (below)
member(X, [X|_]).
member(X, [_|T]) :- member(X,T).
There's a built-in predicate, memberchk/2, that has the same behavior as xmember. When
might it be appropriate to use it instead of member/2?
No
In fact, if the first 3 doesn't work then's no reason to hope that a subsequent 3 will work,
assuming no side-effects. Observe the behavior with memberchk:
No
Here is a recursive predicate that succeeds iff all numbers in a list are positive:
allpos([X]) :- X > 0.
allpos([X|T]) :- X > 0, allpos(T).
Remember that a cut effectively eliminates all subsequent clauses for the active predicate. If
a non-positive value is found the cut eliminates allpos(_). and then the rule fails.
?- allpos([3,-1,5]).
No
A cut-fail is used in the first rule to say that if X is a free variable (i.e., it is uninstantiated),
look no further and fail.
Problem: Add a clause for food/1 that "turns off" all the foods:
?- food(F).
No
The database can be changed dynamically by adding facts with assert/1 and deleting
facts with retract/1.
makefoods :-
assert(food(apple)),
assert(food(broccoli)),
assert(food(carrot)),
assert(food(lettuce)),
assert(food(rice)).
?- food(X).
ERROR: Undefined procedure: food/1
?- makefoods.
Yes
?- food(X).
X = apple
?- retract(food(carrot)).
Yes
?- food(carrot).
No
makefoods :-
retractall(food(_)),
assert(food(apple)),
assert(food(broccoli)),
assert(food(carrot)),
assert(food(lettuce)),
assert(food(rice)).
?- f(X).
X = 1 ;
X = 2 ;
No
?- retract(f(1)), fail.
No
?- f(X).
X = 2 ;
No
There is no notion of directly changing a fact. Instead, a fact is changed by retracting it and
then asserting it with different terms.
A rule to remove foods of a given color (assuming the color/2 facts are present):
Usage:
?- rmfood(green).
Removed broccoli
Removed lettuce
No
?- food(X).
X = apple ;
X = carrot ;
X = rice ;
No
?- run.
? print.
0
? add(20).
? sub(7).
? print.
13
? set(40).
? print.
40
? exit.
Yes
Usage:
?- run0.
? a.
Read a
? ab(c,d,e).
Read ab(c, d, e)
? exit.
Read exit
Yes
repeat/0 always succeeds. If repeat is backtracked into, it simply sends control back to
the right. (Think of its redo port being wired to its exit port.)
do(exit). Yes
run :-
init,
repeat, write('? '), read(X), do(X), X = exit.
Challenge: Add mul and div without having any repetitious code. Hint: Think about
building a structure to evaluate with is/2.
?- tally.
|: to be or
|: not to be ought not
|: to be the question
|: (Empty line ends the input)
-- Results --
be 3
not 2
or 1
ought 1
question 1
the 1
to 3
Yes
readline(Line) :-
current_input(In), read_line_to_codes(In, Codes),
atom_chars(Line, Codes).
tally :- retractall(word(_,_)),
repeat, readline(Line), do_line(Line), Line == '',
show_counts.
do_line('').
do_line(Line) :-
concat_atom(Words, ' ', Line), % splits Line on blanks
member(Word, Words),
count(Word), fail.
do_line(_).
count(Word) :-
word(Word,Count0), retract(word(Word,_)),
Count is Count0+1, assert(word(Word,Count)), !.
count(Word) :- assert(word(Word,1)).
show_counts :-
writeln('\n-- Results --'),
findall(W-C, word(W,C), Pairs),
keysort(Pairs, Sorted),
member(W-C, Sorted),
format('~w~t~12|~w~n', [W,C]),
fail.
show_counts.
a b
c d
e f g
floor
Problem: Define a predicate clean that will print a sequence of blocks to remove from the
floor such that no block is removed until nothing is on it.
% cat blocks.pl
:-dynamic(on/2).
on(a,c). on(a,d).
...
Yes
7 3
5 5
6 4
Problem: Write a predicate laybricks/3 that produces a suitable sequence of bricks for
three rows of a given length:
Usage:
?- getone(X,[a,b,a,d],R).
X = a
R = [b, a, d] ;
X = b
R = [a, a, d] ;
X = a
R = [a, b, d] ;
X = d
R = [a, b, a] ;
Usage:
BricksLeft = [3, 2, 4]
Row = [7] ;
BricksLeft = [2, 7]
Row = [4, 3] ;
No
What were the intermediate steps to arrive at the first row cited?
Usage:
?- laybricks([2,1,3,1,2], 3, Rows).
Rows = [[2, 1], [3], [1, 2]] ;
...many more...
laybricks2([], 0, _, []).
?- laybricks2([5,1,6,2,3,4,3], 3, 8, Rows).
Rows = [[5, 3], [1, 4, 3], [6, 2]]
?- laybricks2([5,1,6,2,3,4,3], 8, 3, Rows).
No
?- laybricks2([5,1,6,2,3,4,3], 4, 6, Rows).
Rows = [[5, 1], [6], [2, 4], [3, 3]]
As is, laybricks2 requires that all bricks be used. How can we remove that requirement?
Usage:
?- b(3,Bricks), laybricks2(Bricks,6,12,Rows).
Bricks = [8, 5, 1, 4, 6, 6, 2, 3, 4|...]
Rows = [[8, 1, 3], [5, 4, 3], [6, 6], [2, 4, 3, 3], [6, 6], [8,
4]]
Yes
Is the getone predicate really necessary? Or could we just use permute/2 and try all
combinations of bricks?
?- statistics.
8.05 seconds cpu time for 25,594,610 inferences
2,839 atoms, 1,854 functors, 1,451 predicates, 23 modules,
35,904 VM-codes
The article asked readers, "Who drinks water? Who owns the zebra?"
We can solve this problem creating a set of goals and asking Prolog to find the condition
under which all the goals are true.
Note that there are five variables: nationality, pet, smoking preference (remember, it was
1962!), beverage of choice and house color. We'll use instances of house structures to
represent knowledge about a house. Anonymous variables are used to represent "don't-
knows".
Some facts can be represented with goals that specify structures as members of Houses with
unknown position:
How can we represent The green house is immediately to the right of the ivory house.?
At hand:
The green house is immediately to the right of the ivory house.
Testing:
?- left_right(Left,Right, [1,2,3,4]).
Left = 1
Right = 2 ;
Left = 2
Right = 3 ;
...
Remaining facts:
The man who smokes Chesterfields lives in the house next to the man with the fox.
Kools are smoked in the house next to the house where the horse is kept.
The Norwegian lives next to the blue house.
The man who smokes Chesterfields lives in the house next to the man with the fox.
Kools are smoked in the house next to the house where the horse is kept.
The Norwegian lives next to the blue house.
We can say that two houses are next to each other if one is immediately left or right of the
other:
More goals:
next_to(house(norwegian, _, _, _, _),
house(_, _, _, _, blue), Houses)
Note that the last two goals ask the questions of interest.
No
Credit: The first part of this section borrows heavily from chapter 12 in Covington, et al.
a boy slept
sentence(Words) :-
article(Words, Left0), noun(Left0, Left1), verb(Left1, []).
Usage:
?- sentence([the,dog,ran]).
Yes
?- sentence([the,dog,boy]).
No
For reference:
sentence(Words) :-
article(Words, Left0), noun(Left0, Left1), verb(Left1, []).
Note that the heads for article, noun, and verb all have the same form.
article([the|Left], Left).
?- article([the,dog,ran], Remaining).
Remaining = [dog, ran]
sentence(Words) :-
article(Words, Left0), noun(Left0, Left1), verb(Left1, []).
?- sentence([the,dog,ran]).
Yes
Each goal consumes one word. The remainder is then the input for the next goal.
For convenient use, let's add a predicate s/1 that uses concat_atom to split up a string
based on spaces:
s(String) :-
concat_atom(Words, ' ', String), sentence(Words).
sentence(Words) :-
article(Words, Left0), noun(Left0, Left1),
verb(Left1, []).
Usage:
Prolog's grammar rule notation provides a convenient way to express these stylized rules.
Instead of this,
sentence(Words) :-
article(Words, Left0), noun(Left0, Left1), verb(Left1, []).
article([the| Left], Left).
article([a| Left], Left).
noun([Noun| Left], Left) :- member(Noun, [dog,cat,girl,boy]).
verb([Verb|Left], Left) :- member(Verb, [ran,talked,slept]).
The semicolon is an "or". Alternative: noun --> [dog]. noun --> [cat]. ...
% cat parser1.pl
sentence --> article, noun, verb.
article --> [a]; [the].
noun --> [dog]; [cat]; [girl]; [boy].
verb --> [ran]; [talked]; [slept].
?- listing(sentence).
sentence(A, D) :- article(A, B), noun(B, C), verb(C, D).
?- listing(article).
article(A, B) :-
( 'C'(A, a, B)
; 'C'(A, the, B)
).
?- listing('C').
'C'([A|B], A, B).
Note that the predicates generated for sentence, article and others have an arity of 2.
% cat parser1a.pl
s(String) :-
concat_atom(Words, ' ', String), sentence(Words,[]).
Points to note:
sentence, article, verb, and noun are known as non-terminals. dog, cat,
ran, talked, etc. are known as terminals.
The call to sentence in s now has two terms to match the generated rule.
verb has both a grammar rule and an ordinary rule. The latter makes use of verb0
facts.
CSc 372, Fall 2006 Prolog, Slide 171
W. H. Mitchell ([email protected])
Goals in grammar rules
We can insert ordinary goals into grammar rules by enclosing the goal(s) in curly braces.
Here is a chatty parser that recognizes the language described by the regular expression a*:
Usage:
?- parse('aaa').
Got an a
Got an a
Got an a
out of a's
Yes
What would the behavior be if the order of the as clauses was reversed?
We can add parameters to the non-terminals in grammar rules. The following grammar
recognizes a* and produces the length, too.
Usage:
?- parse('aaa',N).
N = 3
?- parse('aaab',N).
No
string([N,NN,NNN])
--> as(N), {NN is 2*N}, bs(NN), {NNN is 3*N}, cs(NNN).
?- parse('aabbbbcccccc', L).
L = [2, 4, 6]
?- parse('abbc', L).
No
Usage:
?- parse('abbbcccccc', L).
L = [1, 3, 6]
?- parse('aabbc', L).
No
?- parse('c', L).
L = [0, 0, 1]
Here is a parser that recognizes a string of digits and creates an integer from them:
parse(S,N) :- % parser8.pl
atom_chars(S, Chars), intval(N,Chars,[]), integer(N).
Usage:
?- parse('4312', N).
N = 4312
Here is a parser that recognizes lists consisting of integers and lists: (list.pl)
?- parse('[1,20,[30,[[40]],6,7],[]]').
Yes
?- parse('[1,20,,30,[[40]],6,7],[]]').
No
These parsing examples fall short of what's done in a compiler. The first phase of
compilation is typically to break the input into "tokens". Tokens are things like identifiers,
individual parentheses, string literals, etc.
The input
[ 1, [30+400], 'abc']
The second phase of compilation is to parse the stream of tokens and generate code
(traditional compilation) or execute it immediately (interpretation).
We could use a pair of Prolog grammars to parse source code. The first one would parse
character-by-character and generate a token stream like the list above. The second grammar
would parse that token stream.
In the mid-1990s Christian Collberg developed a system that is able to discover the
instruction set, registers, addressing modes and more for a machine given only a C compiler
for that machine.
The basic idea is to use the C compiler of the target system to compile a large number of
small but carefully crafted programs and then examine the machine code produced for each
program to make inferences about the architecture. The end result is a machine description
that in turn can be used to generate a code generator for the architecture.
The system is written in Prolog. What do you suppose makes Prolog well-suited for this
task?
See http://www.cs.arizona.edu/~collberg/Research/AutomaticRetargeting/index.html
for more details.
The Prolog 1000 is a compilation of applications written in Prolog and related languages.
Here is a sampling of the entries:
AALPS
The Automated Air Load Planning System provides a flexible spatial representation and
knowledge base techniques to reduce the time taken for planning by an expert from
weeks to two hours. It incorporates the expertise of loadmasters with extensive cargo
and aircraft data.
ACACIA
A knowledge-based framework for the on-line dynamic synthesis of emergency
operating procedures in a nuclear power plant.
ASIGNA
Resource-allocation problems occur frequently in chemical plans. Different processes
often share pieces of equipment such as reactors and filters. The program ASIGNA
allocates equipment to some given set of processes. (2,000 lines)
DAMOCLES
A prototype expert system that supports the damage control officer aboard Standard
frigates in maintaining the operational availability of the vessel by safeguarding it and
its crew from the effects of weapons, collisions, extreme weather conditions and other
calamities. (> 68,000 lines)
DUST-EXPERT
Expert system to aid in design of explosion relief vents in environments where
flammable dust may exist. (> 10,000 lines)
EUREX
An expert system that supports the decision procedures about importing and exporting
sugar products. It is based on about 100 pages of European regulations and it is
designed in order to help the administrative staff of the Belgian Ministry of Economic
Affairs in filling in forms and performing other related operations. (>38,000 lines)
GUNGA CLERK
Substantive legal knowledge-based advisory system in New York State Criminal Law,
advising on sentencing, pleas, lesser included offenses and elements.
MISTRAL
An expert system for evaluating, explaining and filtering alarms generated by automatic
monitoring systems of dams. (1,500 lines)
For an honors section assignment Maxim Shokhirev was given the task of finding as many
Prolog implementations as possible in one hour. His results: