24) May 27 - Unification ================================================================================ * The heart of Prolog is an algorithm called 'Unification'. - Input: two terms to be unified: left and right. - Challenge: to find a binding for variables in these terms so that they are the same. 1) How do I unify the following terms? a = b. f(X, b) = f(a, Y). f(X, b) = g(X, b). a(X, X, b) = a(b, X, X). a(X, X, b) = a(c, X, X). a(X, f) = a(X, f). pai(X, Y) = pai(ana, Y). 1.1) Which one is better? {X->ana, Y->maria}, ou {X->ana}? * MGU = The most general unifier \sigma is the MGU of T1=T2 iff, for any \sigma' that unifies T1=T2, then \sigma' is an instance of \sigma. * Unification fills many roles: 2) What is role of unification below: ?- reverse([1,2,3], X). X = [3, 2, 1]. ?- X = 0. X = 0. ?- [1,2,3] = [X|Y]. X = 1, Y = [2, 3]. 3) What is the result of ?- X = f(X)? Or append([], X, [a|X])? - Some implementations of prolog disallow unification with the same variable appearing on the right and on the left side (the occurs check). 4) How to translate the following program to java? p :- q, r. boolean p() {return q() && r();} q :- s. boolean q() {return s();} r :- s. boolean r() {return s();} s. boolean s() {return true;} 4.1) What about p :- p. ? boolean p() {return p();} 5) What about this program? p :- q, r. boolean p() {return q() && r();} q :- s. boolean q() {return s() || true;} q. r. boolean r() {return true;} s :- 0 = 1. boolean s() {return 0 == 1;} * It is not always that simple: we may need substitutions... 6) How to prove p(X) given the program below: p(f(Y)) :- q(Y), r(Y). q(g(Z)). q(h(Z)). r(h(a)). * Let's build an interpreter for prolog, using pseudo-code: fun resolution(clause, goals) let val substitution = MGU(hd(clause), hd(goals)) in substitution(tl(clause) @ tl(goals)) end 6) What is resolution([p(f(Y)), q(Y), r(Y)], [p(X), s(X)])? - [q(Y), r(Y), s(f(Y))] fun solve(goals) if goals = [] then true else foreach (clause c : program) # visit clauses in order if hd(c) unifies with hd(goals) then solve(resolution(c, goals)) else do nothing 7) What is solve([p(X)])? sol([p(X)]) | sol(res([p(f(Y)), q(Y), r(Y)], [p(X)])) {X = f(Y)} | sol([q(Y), r(Y)]) / \ sol(res([q(g(Z))], [q(Y), r(Y)])) sol(res([q(h(Z))], [q(Y), r(Y)])) {Y = g(Z)} {Y = h(Z)} | | sol([r(g(Z))]) sol([r(h(Z))]) | sol(res([r(h(a))], [r(h(Z))])) {Z = a} | sol([]) | true * Proof trees: - two kinds of nodes: nothing's and solve's. - nothing nodes are leaves. - solve nodes are branches. - the root is a solve node containing the list of queries. 8) How to build the tree for solve[p(X)]? {X->f(Y)}[q(Y), r(Y)] / \ {X->f(Y)}{Y->g(Z)}[r(g(Z))] {X->f(Y)}{Y->h(Z)}[r(h(Z))] | {Z->a}[] 9) What is the result of consulting p in: p :- p. p. 9.1) What about: p. p :- p. 9.2) How is the proof tree of each of these programs? * Sometimes variables must be substituted, to guarantee prolog's scope. 10) Do you remember how the "append" predicate was implemented? @([], L, L). @([H|TA], B, [H|TC]) :- @(TA, B, TC). 11) How is the proof tree of solve(@(X, Y, [1, 2])). @(X0, Y0, [1, 2]) / \ {X0=[], Y0=L0, [1,2]=L0} {X0=[H0|TA0], Y0=B0, [H0|TC0]=[1,2]} @(TA0, B0, [2]) / \ {TA0=[], B0=L1, [2]=L1} {TA0=[H1|TA1], B0=B1, [H1|TC1]=[2]} @(TA1, B1, []) | {TA1=[], B1=L2, []=L2} 12) Do you remmeber how reverse was defined? myReverse([], []). myReverse([H|T], X) :- myReverse(T, XAux), myAppend(XAux, [H], X). 12) How is the proof tree of solve([myReverse([1,2], X)]) myReverse([1, 2], X). = myReverse([2], XAux1), @(XAux1, [1], X). = myReverse([], XAux2), @(XAux2, [2], XAux1), @(XAux1, [1], X). = myReverse([], []){XAux2->[]}, @(XAux2, [2], XAux1), @(XAux1, [1], X). = @([], [2], XAux1){XAux1->[2]}, @(XAux1, [1], X). = @([2], [1], X), {X->[2, 1]}. 12.1) What about solve([reverse(X, [1,2])])? * We can use write to debug: reverse([], []). reverse([H|T], X) :- reverse(T, XAux), write(H), write('|'), write(T), write(' '), write(XAux), write('\n'), append(XAux, [H], X). * We can cut the search once we find a valid unification: myReverse([], []). myReverse([H|T], X) :- myReverse(T, XAux), append(XAux, [H], X), !. 12.2) What is the semantics of the cut (!) operator? myReverse([H1|T1], [2, 1]) = myReverse(T1, XAux1), append(XAux1, [H1], [2, 1]), !. = {T1 = [H2|T2]}, myreverse(T2, XAux2), append(XAux2, [H2], XAux1), append(XAux1, [H1], [2, 1]), !. = {T2 = []}, myReverse([], []) => XAux2 = [] append([], [H2], XAux1), append(XAux1, [H1], [2, 1]), !. => XAux1 = [H2] append([H2], [H1], [2, 1]), !. => {H2 = 2, H1 = 1} In Prolog: * A variable which is uninstantiated, i.e. no previous unifications were performed on it, can be unified with an atom, a term, or another uninstantiated variable, thus effectively becoming its alias. In many modern Prolog dialects and in first-order logic, a variable cannot be unified with a term that contains it; this is the so called occurs check. * Two atoms can only be unified if they are identical. * Similarly, a term can be unified with another term if the top function symbols and arities of the terms are identical and if the parameters can be unified simultaneously. Note that this is a recursive behavior. In type theory, the analogous statements are: * Any type variable unifies with any type expression, and is instantiated to that expression. A specific theory might restrict this rule with an occurs check. * Two type constants unify only if they are the same type. * Two type constructors unify only if they are applications of the same type constructor and all of their component types recursively unify. Reflection ========== /* Use this program by feeding the following clauses to the runtime system: ?- assert(connect(a, b)). ?- assert(connect(b, c)). ?- assert(connect(c, d)). ?- assert(connect(d, a)). ?- assert(at(a)). */ report :- write('You are at '), at(You), write(You), nl. step(Dest) :- at(You), connect(You, Dest), retract(at(You)), assert(at(Dest)), write('You just left '), write(You), nl, write('Now you are at '), write(Dest), nl. main :- write('\nNext move - '), read(Move), step(Move).