200 likes | 408 Views
Dave Reed. Prolog programming unification/matching programs as deductive databases resolution, goal reduction declarative vs. procedural readings code tracing lists. Prolog exercise. recall: a Prolog program is a collection of facts & rules that define relations about objects. 0. 1.
E N D
Dave Reed • Prolog programming • unification/matching programs as deductive databases • resolution, goal reduction declarative vs. procedural readings • code tracing • lists
Prolog exercise • recall: a Prolog program is a collection of facts & rules that define relations about objects 0 1 • write Prolog facts & rules that capture the properties of these blocks • constants? predicates? 2 4 3 5 • use Prolog to show that • there is a small block on top of a large block • block 4 is above block 5 • there is a blue block above another blue block • there is nothing above a green block
Prolog unification • when variables are present, inference requires finding appropriate matches between facts & rules • i.e., must Universally Instantiate variables so that facts & rules match mother(M, C) :- parent(M, C), female(M). ?- mother(laura, charlie). requires instance mother(laura,charlie) :- parent(laura,charlie), female(laura). ?- mother(laura, jack). requires instance mother(laura,jack) :- parent(laura,jack), female(laura). • unification is an algorithm for determining the variable instantiations needed to make expressions match • query: mother(laura, charlie) • rule conclusion: mother(M, C) • these expressions unify/match with the variable instantiations: • M = laura, C = charlie
Prolog unification • Prolog's unification algorithm (for matching expressions t1 and t2) • if t1 and t2 are constants, then they match if they are the same object • if t1 is a variable, then they match with t1 instantiated to t2 (i.e., t1 = t2) • if t1 and t2 are function/predicate expressions, then they match if • they have the same function/predicate symbol, AND • all of their arguments match (simultaneously) foo(you) foo(you) match since identical foo(X) foo(you) match, with X = you foo(X) foo(Y) match, with X = Y bar(X,b) bar(a,Y) match, with X = a, Y = b bar(X,f(X)) bar(a,f(a)) match, with X = a foo(X) boo(X) no match, with foo boo foo(X,X) foo(a,f(a)) no match, requires X = a & X = f(a)
Prolog programs as databases • matching/unification alone can provide for computation • consider a database of facts about movies at a video store • %%% to see if Bull Durham is available • ?- movie(_,title('Bull Durham'),_,_,_). • Yes • %%% to find all comedies • ?- movie(type(comedy),title(T),_,_,_). • T = 'My Man Godfrey' ; • T = 'Bull Durham' ; • No • %%% to find movies starring costner • ?- movie(_,title(T),year(Y), • actors(kevinCostner,_),_). • T = 'Dances with Wolves' • Y = 1990 ; • T = 'Bull Durham' • Y = 1988 ; • No %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% movies.pro Dave Reed 1/25/02 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% movie(type(comedy), title('My Man Godfrey'), year(1936), actors(williamPowell,caroleLombard), director(gregoryLaCava)). movie(type(drama), title('Dances with Wolves'), year(1990), actors(kevinCostner,maryMcDonnell), director(kevinCostner)). movie(type(comedy), title('Bull Durham'), year(1988), actors(kevinCostner,susanSarandon), director(ronShelton)). '_' is the anonymous variable, matches anything (but not reported)
Deductive databases • but Prolog databases also allow for deduction • can define new relations that require computation • ?- costars(M, F). • M = williamPowell • F = caroleLombard ; • M = kevinCostner • F = maryMcDonnell • Yes • ?- versatile(Who). • Who = kevinCostner • Yes • ?- modern(Who). • Who = kevinCostner ; • Who = maryMcDonnell ; • Who = kevinCostner ; • No %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% movies.pro Dave Reed 1/25/02 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% movie info as before costars(M,F) :- movie(_,_,_,actors(M,F),_). versatile(P) :- movie(_,_,_,actors(P,_),_), movie(_,_,_,_,director(P)). versatile(P) :- movie(_,_,_,actors(_,P),_), movie(_,_,_,_,director(P)). modern(P) :- movie(_,_,year(Y),actors(P,_),_), Y >= 1990. modern(P) :- movie(_,_,year(Y),actors(_,P),_), Y >= 1990. modern(P) :- movie(_,_,year(Y),_,director(P)), Y >= 1990.
Resolution • the proof procedure for answering queries in Prolog is known as resolution, or goal reduction can think of a query as a goal (or sequence of goals) to be solved or proven can think of the rule H :- B1, B2. as saying • "to solve/prove goal H, solve/prove subgoal B1 and then subgoal B2 " • to solve/prove goals G1, …, Gn: if n = 0, then the answer is 'Yes' else • find a fact or rule whose conclusion matches G1 (searching top-to-bottom) if no match, the answer is 'No' • rename variables so there is no overlap with goal variables • replace G1 with the premises (rhs) of the rule if matched a fact, simply remove G1 from the goal list • instantiate all goals according to the above match • recursively solve the new goal list if successful, the answer is 'Yes' and report all variable bindings else BACKTRACK (go back to goals G1, …, Gn and try next fact/rule)
Resolution example parent(dave, charlie). parent(dave, jack). parent(laura, charlie). parent(laura, jack). male(dave). male(charlie). male(jack). female(laura). father(F, C) :- parent(F, C), male(F). mother(M, C) :- parent(M, C), female(M). • ?- father(F, charlie). • matches the father rule • rename variables in the rule: F1, C1 • match binds: F = F1, C1 = charlie • replace goal with premises, apply binding • • ?- parent(F1, charlie), male(F1). • first goal matches the top fact • match binds: F1 = dave • remove first goal, apply binding • • ?- male(dave). • goal matches fact • remove goal • • ?- • no goals, SUCCESS • answer is the variable binding: F = F1 = dave • F = dave • Yes
Example with backtracking • ?- mother(M, charlie). • matches the mother rule • rename variables in the rule: M1, C1 • match binds: M = M1, C1 = charlie • replace goal with premises, apply binding • • ?- parent(M1, charlie), female(M1). • first goal matches the top fact • match binds: M1 = dave • remove first goal, apply binding • • ?- female(dave). • no match, FAILURE • BACKTRACK to previous goal list • • ?- parent(M1, charlie), female(M1). • continue search after top fact, matches next fact • match binds: M1 = laura • remove first goal, apply binding • • ?- female(laura). • matches fact, remove goal • • ?- • no goals, SUCCESS • answer is the variable binding: M = M1 = laura • M = laura • Yes parent(dave, charlie). parent(dave, jack). parent(laura, charlie). parent(laura, jack). male(dave). male(charlie). male(jack). female(laura). father(F, C) :- parent(F, C), male(F). mother(M, C) :- parent(M, C), female(M).
Dual readings • Prolog programs can be read procedurally H :- B1, B2. "to solve H, solve B1 and B2" • or declaratively: H :- B1, B2. B1 B2 H "H is true if B1 and B2 are true" • under the declarative view: • programming is defining relations using a subset of the predicate calculus • a goal G is true (follows logically from the program) if • there is a fact that matches G, or • there is an instance of a rule with conclusion G whose premises are all true (recursively) • in short, Prolog computation is logical inference logic programming
Declarative vs. procedural • the declarative reading of programs is generally more intuitive • easier to reason with (centuries of research in logic) • static • however, must keep the procedural reading in mind • in reality, rule and premise ordering matters • ancestor(A,P) :- parent(A,P). • ancestor(A,P) :- parent(A,C), ancestor(C,P). is more efficient than • ancestor(A,P) :- parent(A,C), ancestor(C,P). • ancestor(A,P) :- parent(A,P). which is still better than • ancestor(A,P) :- ancestor(C,P), parent(A,C). • ancestor(A,P) :- parent(A,P). WHY?
Ancestor tracing (1) • built-in predicates: • listing will display all facts & relations defining a predicate • trace will cause each inference step to be displayed • in this case, an answer is found quickly since the "simple" case is listed first ?- consult(family). % family compiled 0.16 sec, 72 bytes ?- listing(ancestor). ancestor(A, B) :- parent(A, B). ancestor(A, B) :- parent(A, C), ancestor(C, B). ?- trace. Yes [trace] ?- ancestor(Who, jack). Call: (6) ancestor(_G383, jack) ? creep Call: (7) parent(_G383, jack) ? creep Exit: (7) parent(dave, jack) ? creep Exit: (6) ancestor(dave, jack) ? creep Who = dave Yes
?- listing(ancestor). ancestor(A, B) :- parent(A, C), ancestor(C, B). ancestor(A, B) :- parent(A, B). Yes ?- trace. Yes [trace] ?- ancestor(Who, jack). Call: (7) ancestor(_G386, jack) ? creep Call: (8) parent(_G386, _G441) ? creep Exit: (8) parent(winnie, dave) ? creep Call: (8) ancestor(dave, jack) ? creep Call: (9) parent(dave, _G441) ? creep Exit: (9) parent(dave, charlie) ? creep Call: (9) ancestor(charlie, jack) ? creep Call: (10) parent(charlie, _G441) ? creep Fail: (10) parent(charlie, _G441) ? creep Redo: (9) ancestor(charlie, jack) ? creep Call: (10) parent(charlie, jack) ? creep Fail: (10) parent(charlie, jack) ? creep Fail: (9) ancestor(charlie, jack) ? creep Redo: (9) parent(dave, _G441) ? creep Exit: (9) parent(dave, jack) ? creep Call: (9) ancestor(jack, jack) ? creep Call: (10) parent(jack, _G441) ? creep Fail: (10) parent(jack, _G441) ? creep Redo: (9) ancestor(jack, jack) ? creep Call: (10) parent(jack, jack) ? creep Fail: (10) parent(jack, jack) ? creep Fail: (9) ancestor(jack, jack) ? creep Fail: (9) parent(dave, _G441) ? creep Redo: (8) ancestor(dave, jack) ? creep Call: (9) parent(dave, jack) ? creep Exit: (9) parent(dave, jack) ? creep Exit: (8) ancestor(dave, jack) ? creep Exit: (7) ancestor(winnie, jack) ? creep Who = winnie Yes Ancestor tracing (2) • in this case, finding an answer requires extensive backtracking • harder, recursive case is first • leads to many dead ends
Ancestor tracing (3) • in this case, the initial recursive call leads to an infinite loop • GENERAL ADVICE: • put simpler cases first (e.g., facts before rules) • for recursive rules, put the recursive call last, after previous inferences have constrained variables ?- listing(ancestor). ancestor(A, B) :- ancestor(C, B), parent(A, C). ancestor(A, B) :- parent(A, B). Yes ?- trace. Yes [trace] ?- ancestor(Who, jack). Call: (7) ancestor(_G386, jack) ? creep Call: (8) ancestor(_G440, jack) ? creep Call: (9) ancestor(_G440, jack) ? creep Call: (10) ancestor(_G440, jack) ? creep Call: (11) ancestor(_G440, jack) ? creep Call: (12) ancestor(_G440, jack) ? creep Call: (13) ancestor(_G440, jack) ? creep Call: (14) ancestor(_G440, jack) ? creep Call: (15) ancestor(_G440, jack) ? creep Call: (16) ancestor(_G440, jack) ? creep Call: (17) ancestor(_G440, jack) ? creep . . .
Another example %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% connect.pro Dave Reed 2/5/02 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% road(a, b). road(b, c). road(c, a). road(c, b). road(c, d). road(d, e). road(e, d). connected(City1, City2) :- road(City1, City2). connected(City1, City2) :- road(City1, City3), connected(City3, City2). ?- connected(a, c). Yes ?- connected(c, a). Yes ?- connected(b, d). Yes ?- connected(b, e). ERROR: Out of local stack
Prolog lists • the most general data structure in Prolog is the list • a list is a (possibly empty) sequence of items, enclosed in [ ] [] [foo] [cityA, cityB, cityC] • for a non-empty list, • we say the first item is the HEAD and the rest of the list is the TAIL [foo] HEAD = foo, TAIL = [] [cityA, cityB, cityC] HEAD = cityA, TAIL = [cityB, cityC] • lists are useful & flexible structures (also central to LISP/Scheme)
List notation • [ ] notation for lists is syntactic sugar for function expressions • the dot functor combines the HEAD and TAIL of a list [foo] .(foo, []) [cityA, cityB, cityC] .(cityA, .(cityB, .(cityC, []))) • the dot notation highlights the recursive nature of lists • a list is either • empty, OR • a structure with a HEAD (an item) and a TAIL (a list) • in Prolog, can use either notation ?- [a,b] = .(a, .(b, [])). Yes
List matching • can match variables to specific list items • can match variables to the HEAD and TAIL using '|' • can match variables to multiple items at the front, then the TAIL since a list is really a function expression, can access via matching ?- [X,Y] = [foo, bar]. X = foo Y = bar Yes ?- [H|T] = [a, b, c]. H = a T = [b, c] Yes ?- [H1,H2|T] = [a, b, c]. H1 = a H2 = b T = [c] Yes
List operations • there are numerous predefined predicates for operating on lists • member(X, L): X is a member of list L • recursive definition: X is a member of L if either • X is the HEAD of L, OR • X is a member of the TAIL of L ?- member(a, [a, b, c]). Yes ?- member(a, [b, a, c]). Yes ?- member(a, [b, c]). No member is predefined, but we could define it ourselves if we had to: member(X, [X|_]. member(X, [_|T) :- member(X, T).
Example revisited %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% connect.pro Dave Reed 2/5/02 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% road(a, b). road(b, c). road(c, a). road(c, b). road(c, d). road(d, e). road(e, d). connected(City1, City2) :- pathConnected([City1], City2). pathConnected([City1|_], City2) :- road(City1, City2). pathConnected([CityX|Rest], City2) :- road(CityX, CityY), not(member(CityY,Rest)), pathConnected([CityY,CityX|Rest], City2). • can add loop checking to the connected relation • need to keep track of the path traveled so far • at each step, make sure a city hasn't been visited before adding it to path here, code utilizes a help function with (reversed) path so far as first arg makes use of member to test if visited, built-in not predicate to reverse ?- connected(b, e). Yes ?- connected(e, a). No