510 likes | 674 Views
Prolog. Prolog Keeps us away from the machine Search algorithms are built into the language Clearly defined semantics since it’s based on FOPL Easily extendable—necessary since AI programming is exploratory Basic unit is the rule Scope of variables restricted to a single rule
E N D
Prolog • Keeps us away from the machine • Search algorithms are built into the language • Clearly defined semantics since it’s based on FOPL • Easily extendable—necessary since AI programming is exploratory • Basic unit is the rule • Scope of variables restricted to a single rule • Modifications are simplified Why AI Programming Languages?
Describe the problem in terms of its logical constraints • That is, tell the computer what is true • Pattern search then looks for a solution Task in Prolog
Grew out of work on theorem proving using resolution refutation • Invented in France in the early 70’s as part of work on NLP • Standard Prolog is called Clocksin and Mellish • Developed in the AI lab at the University of Edinburgh in late 70’s History
No control structure • Not guaranteed to halt • CNF is difficult to work with EX: V X (~rain(X) ^ sunny(X) blue_sky(X)) becomes in CNF rain(X) V ~sunny(X) V blue_sky(X) Problems with FOPL
Horn Clauses joined with a depth-first search, backtracking regimen answers all objections • Def Horn Database A database is horn if it is equivalent to set of sentences of the form: a1 ^ … ^am b Where b is a single positive predicate rain(X) ^ cold(X) cloudy(X) Horn Clauses
Most T/F knowledge we have about the world can be described in horn form John is a lawyer or a waiter ~lawyer(john) waiter(john) • Horn DB have attractive computational properties • In a horn DB, the time needed to determine whether or not a particular item follows is O(N*D) • where N is the maximum number of premises in any rule and D is the number of rules in the database • Modus Ponens is capable of finding every valid consequence in a horn DB Properties of Horn Clauses
a b is written b:- a • a ^ b c is written c :- a, b. • ~a ^ b c is written c :- ~a, b. • Facts are entered like this: prof(paul) A Prolog program is a horn database with these modifications
All prolog variables are assumed to be universally quantified Universal Quantifiers
Case 1: The value the existentially quantified variable is known • likes(paul,simcha) is an instance of V X(likes(paul,X)) i.e., there exists X (likes(paul,X)) So likes(paul,simcha) may be entered directly Existential Quantifiers
Case 2: Ask Prolog to find a value that makes the expression true -likes(paul, X) Prolog will determine whether a value of X exists under which the expression is true.
Anything is false whose opposite is not provably true If you query the DB and your query cannot be proved true, then your query is assumed to be false. Suppose we have these predicates in the db: likes(paul,simcha) likes(paul,binky) If we ask likes(paul,skiing) Prolog responds “no” So, all knowledge for a particular universe of discourse is in the DB. If something is not in the DB, that something is false. Closed World Assumption
To start prolog, type swipl (on ada) or prolog (on the VM • To get out: type halt: ?- halt. • To enter a db you’ve created in a file • Store the db with an extension pl (pgm1.pl) • type consult(pgm1): ?- consult(pgm1). or [pgm1] ?- [pgm1] • To see the current DB, type listing: ?- listing. • To trace execution, type trace: ?-trace. • To turn off tracing, type notrace: ?- notrace. Prolog Commands
A list is an ordered collection of elements, consisting of: • Head – the first element in the list • Tail – everything else which is a, possibly empty, list • Syntax: [a|b,c,d] • Head = a • Tail = [b,c,d] • Elements in a list are enclosed w/in brackets: [b,c,d] • Head can be separated from tail with | [a|b,c,d] • _ in H or T position means we don’t care about it: [a|_] Lists
An element is a member of a list if • It is the head of the list • It is a member of the tail • But an element is a member of the tail • If it is the head of the tail • If it is a member of the tail of the tail … Member Checking
mem(A, [A|_). mem(A, [_|Tail) :- mem(A,Tail). More Precisely
Mem(a,[c,b,a]) T Mem(a,[b,a]) R1--F T R1--F Mem(a,[a]) Trace T R1--T
Display the head • Display the head of the tail • Display the head of the tail of the tail • … • Continue this process until the list is empty write(arg) : displays arg write(nl) : causes carriage return/new line Displaying a List
writelist([]). writelist([H|T]) :- write(H), nl, writelist(T). More Precisely
writelist([a,b,c]). R1-F R2 write(a) a write(nl) newline writelist([b,c]) R1-F R2 write(b) b write(nl) newline writelist([c]) R1-F R2 write(c) c write(nl) newline writelist([]) R1-T T T T Trace
reverse(L,R) • Pop items from L • Push them onto an empty list • When L is empty all items will be on the buffer list in reverse order • Copy buffer list to R Reversing Elements in a List
rev(L,R) :- rev1(L, [], R). rev1([], Temp, Temp). rev1([H|T], Temp, R) :- rev1(T,[H|Temp], R). More Precisely
rev([a,b,c],R) rev1([a,b,c], [], R) R1-F rev1([b,c], [a|[]], R) R1-F rev1([c], [b|a], R) R1-F rev1([], [c|b,a], R) R1-T {[c,b,a]/R} T {[c,b,a]/R} T {[c,b,a]/R} T {[c,b,a]/R} T {[c,b,a]/R} T {[c,b,a]/R} Trace
Reverse the List Then Write it out Writing Out a List in Reverse
rev_write1(L) :- rev(L,R), writelist(R). More Precisely
Requires two recursive calls Elegant, but there’s a problem
Take advantage of the fact that reverse unwinds the list and gives access to the head at every level. At the bottom level is the last item in the list: rev(L,R) :- rev1(L, [], R). rev1([], Temp, Temp). rev1([H|T], Temp, R) :- rev1(T,[H|Temp], R). And the single clause, write(H), to the last premise Giving: rev_write2(L,R) :- work(L, [], R). work([], Temp, Temp). work([H|T], Temp, R) :- work(T,[H|Temp], R), write(H). Solutiion
Leads to: rev_write3(L) :- work(L, []). work([], Temp, Temp). work([H|T], Temp) :- work(T,[H|Temp]), nl, write(H). What Else? But notice that we don’t use R
This eliminates the need for a separate clause containing the empty list as an argument. rev_write4([]). rev_write4([H|T]) :- rev_write4(T), nl, write(H). We don’t need to push the head onto the stack.
rev_write4([a,b,c]) R1-F rev_write4([b,c]) R1-F rev_write4([c]) R1-F rev_write4([]) R1-T nl write(c) T nl write(b) T nl write(a) T T Trace
Technique • Pick up list 1 • Slide list 2 under it • Put list 1 down. Appended list is Result append(L1, L2, Result). Appending one list to another
append([], L2, L2) If list 1 is empty, unification copies list 2 to Result Rule 1: Base Case
app(H|T, L2, [H|R]) :- app(T, L2, R). If L1 (i.e., the first arg)has at least 1 item • Copy head of L1 to head of R on LHS • Pop head from L1 on RHS • Eventually, L1 will be empty. • Base case will succeed • R on the RHS holds L2 • As recursion unwinds, the elements of L1 appear at the front of L2 on the RHS in reverse order. Rule 2: Recursion
app([], L, L). app(H|T, L2, [H|R]) :- app(T, L2, R). Append
app([a,b], [c,d], R) {[a,b,c,d]/R} R1-F app([a,b],[c,d], [a|R] if app([b],[c,d],R) {[b,c,d]/R} R1-F app([b],[c,d],[b|R]) if app([],[c,d],R) R1-T {[c,d]/R} Trace
Suppose N is a number and X is a variable Then “X is N + 1” causes X to be bound to the value of N + 1 Technique • Basis • Length of list with no elements is 0 • Len([],0) • Recursive step? Length of a List
Length of a non-empty list is the length of its tail + 1 • Len([H|T],L) :- len(T, Lt), L is Lt + 1. Recursive step
len([a,b,c],L) R1-F len([a|b,c], L) if len([b,c],Lt), L is Lt + 1 R1-F len(b,c],L) {2/Lt} if len([c], Lt), L is Lt + 1 {1/Lt} R1-F len([c],L) if len([],Lt), L is Lt + 1 {1/L} R1-T {0/Lt} Trace
Stacks • Queues Two Data Structures
A stack is empty if the list is the empty list • empty([]) Empty
Argument 1 is the element to be pushed • Argument 2 is the stack • Argument 3 is the new stack • push(Top, Stack, [Top|Stack]) Push
Same as push, but this time the second argument is the new stack • pop(Top, Stack, [Top|Stack]) Pop
We can use our member program • isIn(Elt,Stack) :- mem(Elt,Stack) isIn
Technique • Pop the stack until empty • Then write elements as recursion unwinds Write (Reverse)
write_r(Stack) :- empty(Stack). write_r(Stack) :- pop(Top,Rest,Stack), write_r(Rest), write(Top), nl.
A little harder since we have to traverse the underlying list either for enqueue Queue
empty([]). • isIn(Elt,Stack) :- mem(Elt,Stack) • serve(Top, Stack, [Top|Stack]) Empty, isIn, and serve are identical to empty, isIn, and pop for stack
Necessary to recursively traverse the list • If the queue is empty, the enqueued element is the queue • enqueue(Elt, [], [Elt]). • Else pick up the queue, item by item, slide in the new element and replace the queue • enqueue(Elt, [H|T], [H|Tnew]) :- enqueue(Elt, T, Tnew). Enqueue
writeQ(Queue) :- empty(Queue). writeQ(Queue) :- serve(Top,Rest,Queue), write(Top), nl, writeQ(Rest). Writing the Queue is easy
Basis: person A is the ancestor of person B, if person A is the parent of person B. • ancestor(A,B) :- parent(A,B). • Recursive Step: Person A is the ancestor of person C, if person A is the ancestor of person B and person B is the ancestor of person C • ancestor(A,C) :- parent(A,B), ancestor(B,C). Recursion without listsThe Ancestor Problem