1 / 37

Implementing Prolog

Implementing Prolog. Outline. Parts of Chapter 20, with implementation 20.2 Unification 20.4 Implementing Prolog From 20.5: Variable renaming. Substitutions. A substitution is a function that maps variables to terms:  = { X  a , Y  f(a,b) }

tarika
Download Presentation

Implementing Prolog

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Implementing Prolog Modern Programming Languages

  2. Outline • Parts of Chapter 20, with implementation • 20.2 Unification • 20.4 Implementing Prolog • From 20.5: Variable renaming Modern Programming Languages

  3. Substitutions • A substitution is a function that maps variables to terms: = {Xa, Yf(a,b)} • This  maps X to a and Y to f(a,b) • The result of applying a substitution to a term is an instance of the term • (g(X,Y)) = g(a,f(a,b)) so g(a,f(a,b)) is an instance of g(X,Y) Modern Programming Languages

  4. Unification • Two Prolog terms t1 and t2unify if there is some substitution  (their unifier) that makes them identical: (t1) = (t2) • a and b do not unify • f(X,b) and f(a,Y) unify: a unifier is {Xa, Yb} • f(X,b) and g(X,b) do not unify • a(X,X,b) and a(b,X,X) unify: a unifier is {Xb} • a(X,X,b) and a(c,X,X) do not unify • a(X,f) and a(X,f) do unify: a unifier is {} Modern Programming Languages

  5. Multiple Unifiers • parent(X,Y) and parent(fred,Y): • one unifier is 1 = {Xfred} • another is 2 ={Xfred, Ymary} • Prolog chooses unifiers like 1 that do just enough substitution to unify, and no more • That is, it chooses the MGU—the Most General Unifier Modern Programming Languages

  6. MGU • Term x1 is more general than x2 if x2 is an instance of x1 but x1 is not an instance of x2 • Example: parent(fred,Y) is more general than parent(fred,mary) • A unifier 1 of two terms t1 and t2 is an MGU if there is no other unifier 2 such that 2(t1) is more general than 1(t1) • MGU is unique up to variable renaming Modern Programming Languages

  7. Unification For Everything • Parameter passing • reverse([1,2,3],X) • Binding • X=0 • Data construction • X=.(1,[2,3]) • Data selection • [1,2,3]=.(X,Y) Modern Programming Languages

  8. The Occurs Check • Any variable X and term t unify with {Xt}: • X and b unify: an MGU is {Xb} • X and f(a,g(b,c)) unify: an MGU is {Xf(a,g(b,c))} • X and f(a,Y) unify: an MGU is {Xf(a,Y)} • Unless X occurs in t: • X and f(a,X)do not unify, in particular not by {Xf(a,X)} Modern Programming Languages

  9. Occurs Check Example append([], B, B).append([Head|TailA], B, [Head|TailC]) :- append(TailA, B, TailC). • Most Prologs omit the occurs check • ISO standard says the result of Prolog “unification” is undefined in cases that should fail the occurs check ?- append([], X, [a | X]).X = [a, a, a, a, a, a, a, a, a|...] Yes Modern Programming Languages

  10. ML Implementation • We will look at an implementation of Prolog in ML • First, some code for representing terms Modern Programming Languages

  11. Representing Terms (* A datatype for Prolog terms. A term can be an Atom, a Variable, or a Compound term. We represent all the lexemes using strings. *) datatype term = Atom of string | Variable of string | Compound of string * term list; Modern Programming Languages

  12. Example Prolog Clause (* p(f(Y)) :- q(Y),r(Y). *) val c1 = [Compound("p",[Compound("f",[Variable("Y")])]), Compound("q",[Variable("Y")]), Compound("r",[Variable("Y")])]; Modern Programming Languages

  13. Substitution (* A special mapping function for terms. We convert a term into the new term formed by applying the function f to all the variables (but leaving all the atoms and predicate names unaltered). *) fun mapVariable f (Atom x) = Atom(x) | mapVariable f (Variable n) = f n | mapVariable f (Compound(n, terms)) = Compound(n, map (mapVariable f) terms); (* Create a substitution. We take a variable name and a term, and return a function that can be used to apply that substitution to any term. *) fun sub name term = mapVariable (fn n => if n=name then term else Variable n); Modern Programming Languages

  14. Unification (* Find a most general unifier of two terms. We return a pair of values, like this: val (unifiable, unifier) = mgu(a,b) The first value we return is a boolean, true iff the two terms are unifiable. If they are, the second value we return is a most general unifier: a function that, when applied to a and b, makes them identical. We do not perform the occurs check. *) fun mgu(a,b) = let fun ut([], [], unifier) = (true, unifier) | ut(term::t1, Variable(name)::t2, unifier) = let val r = (sub name term) o unifier in ut(map r t1, map r t2, r) end | ut(Variable(name)::t1, term::t2, unifier) = let val r = (sub name term) o unifier in ut(map r t1, map r t2, r) end Modern Programming Languages

  15. More Unification | ut(Atom(n)::t1, Atom(m)::t2, unifier) = if n=m then ut(t1,t2,unifier) else (false, unifier) | ut(Compound(n1,xt1)::t1, Compound(n2,xt2)::t2, unifier) = if n1=n2 andalso length xt1 = length xt2 then ut(xt1@t1, xt2@t2, unifier) else (false, unifier) | ut(_,_,unifier) = (false, unifier); in ut([a],[b], (fn x => x)) end; Modern Programming Languages

  16. About Unification Algorithms • The one just shown takes exponential time in the worst case • In fact, any algorithm that linearly represents the unified term, without shared structure, takes exponential time and space • Using fancier techniques, it can be done in linear time Modern Programming Languages

  17. Outline • Parts of Chapter 20, with implementation • 20.2 Unification • 20.4 Implementing Prolog • From 20.5: Variable renaming Modern Programming Languages

  18. Resolution • The hardwired inference step • A clause is represented as a list of terms (a list of one term, if it is a fact) • Resolution step applies one clause, once, to make progress on a list of goal terms function resolution(clause, goals):let sub = the MGU of head(clause) and head(goals)return sub(tail(clause) concatenated with tail(goals)) Modern Programming Languages

  19. Resolution Example Given this list of goal terms:[p(X),s(X)]And this rule to apply:p(f(Y)) :- q(Y), r(Y).The MGU of the heads is {Xf(Y)}, and we get:resolution([p(f(Y)),q(Y),r(Y)], [p(X),s(X)]) = [q(Y),r(Y),s(f(Y))] function resolution(clause, goals):let sub = the MGU of head(clause) and head(goals)return sub(tail(clause) concatenated with tail(goals)) Modern Programming Languages

  20. Resolution in ML fun resolution(head::conds,goal::goals) = let val (unifiable, unifier) = mgu(head,goal) in map unifier (conds@goals) end; function resolution(clause, goals):let sub = the MGU of head(clause) and head(goals)return sub(tail(clause) concatenated with tail(goals)) Modern Programming Languages

  21. A Prolog Interpreter function solve(goals)if goals is empty then succeed()else for each clause c in the program, in orderif head(c) does not unify with head(goals) then do nothingelse solve(resolution(c, goals)) Modern Programming Languages

  22. Program: 1. p(f(Y)) :- q(Y),r(Y).2. q(g(Z)).3. q(h(Z)).4. r(h(a)). A partial trace for query p(X): solve([p(X)]) 1. solve([q(Y),r(Y)]) … 2. nothing 3. nothing 4. nothing • solve tries each of the four clauses in turn • The first works, so it calls itself recursively on the result of the resolution step (not shown yet) • The other three do not work: heads do not unify with the first goal term Modern Programming Languages

  23. Program: 1. p(f(Y)) :- q(Y),r(Y).2. q(g(Z)).3. q(h(Z)).4. r(h(a)). A partial trace for query p(X), expanded: solve([p(X)]) 1. solve([q(Y),r(Y)])1.nothing2. solve([r(g(Z))]) …3. solve([r(h(Z))]) …4.nothing2.nothing3.nothing4.nothing Modern Programming Languages

  24. Program: 1. p(f(Y)) :- q(Y),r(Y).2. q(g(Z)).3. q(h(Z)).4. r(h(a)). A complete trace for query p(X): solve([p(X)]) 1. solve([q(Y),r(Y)]) 1.nothing2. solve([r(g(Z))]) 1.nothing2.nothing3.nothing4.nothing3. solve([r(h(Z))]) 1.nothing2.nothing3.nothing4. solve([]) —success!4.nothing2.nothing3.nothing4.nothing Modern Programming Languages

  25. Collecting The Substitutions function resolution(clause, goals, query):let sub = the MGU of head(clause) and head(goals)return (sub(tail(clause) concatenated with tail(goals)), sub(query)) function solve(goals, query)if goals is empty then succeed(query)else for each clause c in the program, in orderif head(c) does not unify with head(goals) then do nothingelse solve(resolution(c, goals, query)) • Modified to pass original query along and apply all substitutions to it • Proved instance is passed to succeed Modern Programming Languages

  26. Program: 1. p(f(Y)) :- q(Y),r(Y).2. q(g(Z)).3. q(h(Z)).4. r(h(a)). A complete trace for query p(X): solve([p(X)],p(X)) 1. solve([q(Y),r(Y)],p(f(Y))) 1.nothing2. solve([r(g(Z))],p(f(g(Z)))) 1.nothing2.nothing3.nothing4.nothing3. solve([r(h(Z))],p(f(h(Z)))) 1.nothing2.nothing3.nothing4. solve([],p(f(h(Z))))4.nothing2.nothing3.nothing4.nothing Modern Programming Languages

  27. Prolog in ML fun solve(_,nil,query) = succeed query | solve(program,goal::goals,query) = let fun onestep (head::conds, _) = let val (unifiable, unifier) = mgu(head,goal) in if unifiable then solve(program, map unifier (conds@goals), unifier query) else true end in foldl onestep true program end; fun prolog(program,query) = solve(program,[query],query); Modern Programming Languages

  28. Outline • Parts of Chapter 20, with implementation • 20.2 Unification • 20.4 Implementing Prolog • From 20.5: Variable renaming Modern Programming Languages

  29. A Problem • Model of Prolog execution just shown is flawed, as is the ML Prolog implementation • It works on some examples • On others it does not agree with common sense, or with the actual behavior of a Prolog language system • For instance, reverse([1,2],X) Modern Programming Languages

  30. Program: 1. reverse([],[]).2. reverse([Head|Tail],X) :- reverse(Tail,Y), append(Y,[Head],X). A trace for query reverse([1,2],X) solve([reverse([1,2],X)]) 1. nothing 2. solve([reverse([2],Y),append(Y,[1],X)]) 1.nothing2. solve([reverse([],X), append(X,[2],X), append(X,[1],X)]) 1.solve([append([],[2],[]), append([],[1],[])])…no solution: what went wrong?2.nothing Modern Programming Languages

  31. Program: 1. reverse([],[]).2. reverse([Head|Tail],X) :- reverse(Tail,Y), append(Y,[Head],X). This step was wrong; we substituted X for Y, but there is already an X in the goal list A trace for query reverse([1,2],X) solve([reverse([1,2],X)]) 1. nothing 2. solve([reverse([2],Y),append(Y,[1],X)]) 1.nothing2. solve([reverse([],X), append(X,[2],X), append(X,[1],X)]) 1.solve([append([],[2],[]), append([],[1],[])])…no solution2.nothing Modern Programming Languages

  32. Capture • Recall Prolog’s scope rule: the scope of a definition of a variable is the clause containing it • Our resolution step mashes two scopes together, incorrectly identifying any variables that happen to have the same names • Another kind of capture Modern Programming Languages

  33. Variable Renaming • To avoid capture, use fresh variable names for each clause, every time you apply it • The first application of reverse might be: • And the next might be: • And so on… reverse([Head1|Tail1],X1) :- reverse(Tail1,Y1), append(Y1,[Head1],X1). reverse([Head2|Tail2],X2) :- reverse(Tail2,Y2), append(Y2,[Head2],X2). Modern Programming Languages

  34. Program: 1. reverse([],[]).2. reverse([Head|Tail],X) :- reverse(Tail,Y), append(Y,[Head],X). A trace for query reverse([1,2],X) solve([reverse([1,2],X)]) 1. nothing 2. solve([reverse([2],Y1),append(Y1,[1],X1)]) 1.nothing2. solve([reverse([],Y2), append(Y2,[2],X2), append(X2,[1],X1)]) 1.solve([append([],[2],X2), append(X2,[1],X1)])…solution with X2=[2], X1=[2,1] 2.nothing Modern Programming Languages

  35. ML Renaming • This ML implementation solves the problem by renaming • It adds “#n” to the end of every variable name whenever a clause from the program is used, where n is the depth of the recursion • Real Prologs find a more efficient way to do this, of course Modern Programming Languages

  36. fun rename iter term = mapVariable (fn n => Variable(n^"#"^iter)) term; fun solve(_,nil,query,_) = succeed query | solve(program,goal::goals,query,depth) = let fun onestep (clause, _) = let val head::conds = map (rename (Int.toString depth)) clause val (unifiable, unifier) = mgu(head,goal) in if unifiable then solve(program, map unifier (conds@goals), unifier query, depth+1) else true end in foldl onestep true program end; fun prolog(program,query) = solve(program,[query],query,1); Modern Programming Languages

  37. Conclusion • Prolog has a simple model of execution • Compare with implementations of similar interpreters for Java or ML • (We’ll use Prolog to write some interpreters for ML-like languages in Chapter 23) • Interestingly: using Prolog is tricky, in spite of its simple interpreter and simple syntax • ML is a good language for this kind of thing Modern Programming Languages

More Related