father(john,ralph). father(mary,ralph). father(ralph,bill). mother(john,sue). mother(mary,sue). mother(ralph,joan). /* RULES */ is_mother(M):-mother(Y,M). parent(X,M):-mother(X,M). parent(X,F):-father(X,F). ancester(X,Y):-parent(X,Y). ancester(X,Y):-parent(X,Z),ancester(Z,Y). ?-ancester(X,joan).backtracking
First succeeds with X = ralph
later w/X= john, mary
Usually in prefix, can force to be in infix or postfix and give precedence as well.
Ex. +, - ,*,/
2+3*5 abbreviates +(2,*(3,5))
operations are not evaluated
Better to think of operations as forming tags on values -
=, \=, <, >, =<, >= (note order of composite relations) are evaluated.
Ex. digit(N):- N>=0,N<10.
Example of using operations.
tree is either nil or is of form maketree(tree1,X,tree2).
Write program to do treesort! Very much like ML program.
Ex. area(L,W,A):-A is L*W.
Can only compute A from L,W - not reversible.
edge(a,b). edge(a,f). edge(b,c). edge(c,a). edge(d,e). edge(e,a). edge(e,c). dumb_path(Start,Finish) :- edge(Start,Finish). dumb_path(Start,Finish) :- edge(Start,Next),dumb_path(Next,Finish).
What happens if type:
?- dumb_path(a,c). ?- dumb_path(a,e).
Problem is continues to go through same vertex multiple times.
Smarter program keeps track of vertices which are visited.
path(Start,Finish) :- smart_path(Start,Finish,). smart_path(Current,Target,Visited) :- edge(Current,Target). smart_path(Current,Target,Visited) :- edge(Current,Next),non_member(Next,Visited), smart_path(Next,Target,[Next|Visited]). non_member(Elt,). non_member(Elt,[Hd | Tl]) :- Elt \== Hd, non_member(Elt,Tl).
Note that database representation of graph is not only possibility.
Can use adjacency list representation.
Write graph as list of vertices and corresponding edges:
Each vertex included with list of all edges going from it.
E.g. node a represented by
v(a,[b,f])Whole graph given by
[v(a,[b,f]), v(b,[c]), v(c,[a]), v(d,[e]),v(e,[a,c])].Advantage - can add vertices and edges during a computation.
vertex(Graph,Vertex) which tells if Vertex is in Graph:
vertex([v(Vert,Edge) | Rest],Vert). vertex([_ | Rest],Vert) :- vertex(Rest,Vert).edge(Graph,X,Y) true if there is an edge from X to Y.
edge(Graph,X,Y) :- member(v(X,Edges),Graph), member(Y,Edges). edge(Graph,X,Y) :- member(v(Y,Edges),Graph), member(X,Edges).
Rest of program for paths is as before.
Russian farmer puzzle (variation of missionary and cannibals).
Farmer taking goat and (giant) cabbage to market. Wolf following farmer. Come to river with no bridge, but only tiny boat big enough to hold farmer and one object. How can farmer get all three across river w/out goat eating cabbage or wolf eating goat?
At any time during solution, describe current state by noting which side of river each of farmer, goat, cabbage and wolf are on (call them north/south).
Can write down all states and all allowable transitions and then find path. (Like finding path in graph!)
But must be smarter way!
Really only 4 possible actions: Farmer crosses with one of goat, cabbage, and wolf, or farmer crosses alone.
Write down rules for each.
Describe states by terms - state(F,G,C,W) where each of F,G,C,W is one of north, south.
Need predicate "opposite"
opposite(north,south). opposite(south,north). /*Action 1 - Farmer crosses with goat - need farmer and goat to start on same side, no danger of wolf eating cabbage. */ transition(state(F0,F0,C,W),state(F1,F1,C,W)) :- opposite(F0,F1). /*Action 2 - Farmer crosses with cabbage - need farmer and cabbage to start on same side, wolf and goat must be on opposite sides of river so goat is safe. */ transition(state(F0,G,F0,W),state(F1,G,F1,W)) :- opposite(F0,F1), opposite(G,W). /*Action 3 - Farmer crosses with wolf - need farmer and wolf to start on same side, goat must be on opposite side from cabbage. */ transition(state(F0,G,C,F0),state(F1,G,C,F1)) :- opposite(F0,F1), opposite(G,C). /*Action 4 - Farmer crosses alone - need cabbage and wolf on same side, goat on opposite side. */ transition(state(F0,G,C,C),state(F1,G,C,C)) :- opposite(F0,F1),opposite(G,C).
Finding solution to problem is like finding path in graph.
Homework: Find if there is a solution. If so, print it nicely! (Must retain path!)
Show hanoi1.p to see how to use printing.
pred(...):- cond1,...,condk, ! ,condk+1,..., condn.
! - is always satisfied - freezes all previous choices with pred.
If get to point where no more solns to condk+1,..., condn then all of pred(...) fails and no other clause or rule for pred will hold.
Backtracks until ! satisfied and then never backtracks over it.
1. sum_to(N,X) should give 1 + 2 + ... + N.
sum_to(1,1). sum_to(N,Sum) :- Pred is N-1, sum_to(Pred,Partial_sum), Sum is Partial_sum + N.
If ever try to resatisfy sum_to(1,X) will go into infinite loop. (Why?)
Most likely happens as part of resatisfying something complex. e.g. sum_to(1,X),foo(apples).
Fix by putting in sum_to(1,1) :- !.
Ex. try to resatify various sorts, get wrong answers. Really only one correct answer!
Try insert sort, selection sort.
If can exclude some cases early on. Can use cut to terminate search.
Possible_Pres(rush) :- !,fail. Possible_Pres(X) :- NativeBorn(X).
fail is predicate that is never satisfied.
Sometimes get strange behavior if not careful.
Suppose have following program:
likes(joe,pizza) :- !. likes(jane,Anything).
What happens if put in query: ?- like(Person,pizza).
Get answer of joe, but not jane. Yet likes(jane,pizza) is true!
Unfortunately, putting in cut may screw up reversibility.
append(,X,X) :- !. append([A|B],C,[A|D]) :- append(B,C,D).Now only get one answer (when running either direction).
Understand program as either proof or set of procedure calls.
is just treated as an atomic statement which is asserted to be true.
Rule: A(X,Y) :- B(X,Y),C(X,Y).
is understood as for all X,Y (B(X,Y) ^ C(X,Y) -> A(X,Y))
Formulas of the form for all X1,...,Xm (B1(X) ^...^ Bn(X) -> A(X)) are said to be Horn clause formulas (notice fact is degenerate case where n = 0).
Program is understood as a collection of Horn clause formulas.
Query: ?- D(X,Y)
is understood as there exists X,Y. D(X,Y).
Is there a proof of there exists X,Y. D(X,Y) from statements in program.
Resolution theorem proving works by attempting to show that hypotheses and negation of conclusion (i.e. for all X,Y. ~ D(X,Y)) generate a contradiction. Contradiction is essentially found by finding values for X, Y such that D(X,Y).
is_own_successor :- X = successor(X).
return true, even in absence of other info about successor.
is_own_successor(X) :- X = successor(X).then prints infinite term:
X = successor(successor(successor(successor(successor(...(Can also occur if try to unify clauses containing A(Y,f(Y)) and ~A(Z,Z) )
But no finite term which satisfies - "occurs check" would rule this out.
Can lead to incorrect conclusions.
Just as depth-first search can lead to missing correct solutions.
Occurs check used to be believed to be exponential time. Now have faster (but more complex) algorithms.
Changed status from being a "bug" to being a "feature".
In Prolog negation actually based on finite failure. That is report something false if no proof that it is true and the attempt to prove it terminates.
Recall 3 possible outcomes of proof. Succeeds, fails, doesn't terminate.
Returns false only if it fails (finite failure).
If attempt never terminates then don't report it false!.
Note that this is a non-monotonic rule of reasoning.
E.g., if have program above (w/father, mother, etc), then since can't find or prove
father(shezad,kim), system deduces that it is false.
Built-in predicate, "not", defined so that:
not(X) succeeds if an attempt to satisfy X fails.
not(X) fails if an attempt to satisfy X succeeds.
not(X) :- X,!,fail. not(X).
?- not(father(shezad,kim)).reports true
However if add fact, father(shezad,kim), then will reverse answers.
What happens if write:
?- not(Z) where Z has not been unified
childless(X) :- not(father(Y,X)),not(mother(Z,X)).then childless(fred) will return yes if fred not mentioned in program!
whereas childless(Z) returns no.
old(X) :- not(young(X)).and don't have facts or rules for young, then everything is old!
If write the other direction,
young(X) :- not(old(X)).get the opposite response, everything is young!
Seems to fly in the face of closed-world assumption!
Not does not always behave properly - in particular, not(not(term)) does usually not return same answers as term.
Safest if only use not on predicates which have no free variables (i.e., make sure variables instantiated before calling it!). See homework for problems otherwise.
Control structure implicit, but very important!
Keep in mind difference between PROLOG and pure (Horn clause) logic programming
Limits effectiveness to 2 classes of programs:
Retains useful role as well for prototyping or as specification language.
One of reasons Japanese chose PROLOG is belief that it can be speeded up by use of highly parallel machines. OR-parallelism seems useful (work on different branches of search tree in parallel), but AND-parallelism (trying to satisfy various terms of right side of rule in parallel) seems more problematic.