As a step towards implementing an experimental type inferencing or type checking system I have built a prolog interpreter in Church. The interpreter is based on the example in Peter Norvig’s book, “Paradigms of Artificial Intelligence Programming”.
As a first step I wrote the grammar for parsing prolog programs. Here is a sample ‘parent’ program describing parent and grandparent relationships between Debian derivatives:
parent(ubuntu,kubuntu).
parent(ubuntu,edubuntu).
parent(debian, ubuntu).
grandparent(X,Z) :- parent(X,Y), parent(Y,Z).
and here is the OMeta grammar for parsing prolog programs:
ometa prolog <: ometa {
comment = "/*" (~cnewline anything)* cnewline -> << '_comment >>,
ws = $ | $ ,
wsnl = ws | cnewline,
prolog-top-levels = (comment | prolog-rule)*,
prolog-rule = wsnl* prolog-clause:rhead ws* (":-" ws* prolog-or:body -> << body >>)* ws* $. wsnl* -> <<`[rule ,rhead ,body]>>,
prolog-or = listof("prolog-impl", ";"):e wsnl* -> << `[or ,e] >>,
prolog-impl = wsnl* prolog-clause:a wsnl* "->" wsnl* prolog-or:b wsnl* -> << `[implies ,a ,b] >> | prolog-and,
prolog-and = wsnl* listof("prolog-expr", ","):e wsnl* -> << `[and ,e] >>,
prolog-clause = wsnl* prolog-name:name wsnl* prolog-arg-list:args wsnl* -> <<`[,name | ,args]>>,
prolog-arg-list = $( listof("prolog-expr", ","):args wsnl* $) wsnl* -> << args >>,
prolog-expr = wsnl* (
$[ wsnl* $] wsnl* -> << `[]>> |
$[ wsnl* listof("prolog-expr", ","):list-head ws* ($| wsnl* prolog-expr)*:list-tail wsnl* $] wsnl* -> << (append! list-head (if list-tail (first list-tail) nil)) >> |
$( wsnl* prolog-or:e wsnl* $) wsnl* -> << e >> |
prolog-clause:c wsnl* -> << c >> |
prolog-number |
prolog-variable:v wsnl* -> << v >>),
prolog-number = "-"*:sign digit+:d -> << (convert-number sign d)>>,
prolog-variable = (letter | digit | $_)+:l -> << (intern (coerce l 'string)) >>,
prolog-name = (letter | digit | $_)+:l -> << (intern (coerce l 'string)) >> }
The core of prolog lies in the unification and backtracking algorithms.
Unification will try to match its two inputs or else bind a variable to the corresponding value:
unify x y bindings
cond
(eq? bindings prolog-fail) prolog-fail
(eq? x y) bindings
(eq? x '_) bindings
(eq? y '_) bindings
(variable-symbol? x) (unify-variable x y bindings)
(variable-symbol? y) (unify-variable y x bindings)
(and (cons? x) (cons? y)) (unify (rest x) (rest y) (unify (first x) (first y) bindings))
true prolog-fail
unify-variable var x bindings
cond
(get-binding var bindings) (unify (lookup var bindings) x bindings)
(and (variable-symbol? x) (get-binding x bindings)) (unify var (lookup x bindings) bindings)
(and occurs-check? (occurs-check var x bindings)) prolog-fail
true (extend-bindings var x bindings)
Backtracking is achieved by allowing each clause to provide multiple solutions and trying all of these possibilities:
prove-all pi:prolog-interpreter goals bindings
cond
(eq? bindings prolog-fail) nil
(null? goals) (list bindings)
true
mapcan (fn goal1-solution
prove-all pi (rest goals) goal1-solution
) (prove pi (first goals) bindings)
As a test I used this map coloring program from a prolog tutorial:
member(X,[X|_]).
member(X,[_|List]) :- member(X,List).
adjacent(X,Y,Map) :- member([X,Y],Map) ; member([Y,X],Map).
find_regions([],R,R).
find_regions([[X,Y]|S], R,A) :-
(member(X,R) ->
(member(Y,R) -> find_regions(S,R,A) ; find_regions(S,[Y|R],A)) ;
(member(Y,R) -> find_regions(S,[X|R],A) ; find_regions(S,[X,Y|R],A) )).
color(Map,Colors,Coloring) :-
find_regions(Map,[],Regions),
color_all(Regions,Colors,Coloring),
not(conflict(Map,Coloring)).
color_all([R|Rs],Colors,[[R,C]|A]) :-
member(C,Colors),
color_all(Rs,Colors,A).
color_all([],_,[]).
conflict(Map,Coloring) :- member([R1,C],Coloring),
member([R2,C],Coloring),
adjacent(R1,R2,Map).
map1([[1,2],[1,3],[1,4],[1,5],[2,3],[2,4],[3,4],[4,5]]).
which yields the following colourings:
"query"
[[map1 M] [color M [red green blue yellow] Coloring]]
M = [[1 2] [1 3] [1 4] [1 5] [2 3] [2 4] [3 4] [4 5]]
Coloring = [[5 red] [4 green] [3 red] [1 blue] [2 yellow]]
;
M = [[1 2] [1 3] [1 4] [1 5] [2 3] [2 4] [3 4] [4 5]]
Coloring = [[5 red] [4 green] [3 red] [1 yellow] [2 blue]]
...
You can browse the source files for the prolog interpreter here.
Prolog is an interesting language but I prefer taking its building blocks backtracking and unification and integrating them into the host language instead of building interpreter.
Do you have any examples of this kind of integration that you admire?
You could add wrapper around the prolog queries. For example a simple macro like :
(defact father ‘Jon ‘Tim)
(defact father ‘Jon ‘Sara)
(defact father ‘Ron ‘Jon)
(defrule father (son-name father-name) ..
that creates function that will search through the facts in the prolog database.
(father ‘Jon _) => Tim
(father _ ‘Tim) => Jon
Since prolog is relational you need to make some convention about mutiple results returned.
You can return list:
(father ‘Jon _) => (Tim Sara)
or stream or a table like the database project I started working on which I will hope to continue to work on after we ship this May.