Ex.

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 -

- forming records -
- don't really compute anything -
- typically uninterpreted -
- very much like user-defined types in ML

Relations

=, \=, <, >, =<, >= (note order of composite relations) are evaluated.

Ex. `digit(N):- N>=0,N<10. `

Example of using operations.

treesort

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.

Write:

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?

Specify sol'n.

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.

- When get to this position, no other rule should be applicable, thus never try another.
- When get to this point, no hope of ever succeeding.
- Only 1 sol'n of interest - don't even try generating others.

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.

E.g.

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.

__Fact__: A(a,b,c)

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).

Ex:

is_own_successor :- X = successor(X).

return true, even in absence of other info about successor.

If write

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).

Thus

?- 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

If define

childless(X) :- not(father(Y,X)),not(mother(Z,X)).then

whereas `childless(Z)` returns no.

If define

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

- PROLOG has many faults
- Idea of logic programming may still be quite promising if can overcome deficiencies of PROLOG. (For example, see uses of Datalog in knowledge-based database.)
- If when programming, can ignore issues of control and just worry about logic, then when works can worry about control to optimize without destroying correctness.
- Very high level - self documenting.
- Efficiency is problem with PROLOG. (Can optimize with tail-recursion as in ML!)

Limits effectiveness to 2 classes of programs:

- Where efficiency not a consideration.
- Where too complex for conventional language.

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.

- One of few examples of non-procedural languages. (although bit of a lie)
- Generalize by replacing unification by, e.g., solving systems of
inequalities.

Constraint logic programming.