David Nolen mentioned Ivan Bratko’s Prolog Programming For Artificial Intelligence at a talk he gave recently demoing cKanren extensions in Core.Logic. I eagerly picked up an old edition of the book for $5 and got to work.
My other exposure to logic programming has been through the prolog compiler that Peter Norvig presents in Paradigms of Artificial Intelligence Programming and studying mini-Kanren via Friedman’s The Reasoned Schemer. Dan Friedman and William Byrd also gave a presentation at a Clojure conj.
Chapter 1 of Ivan Bratko’s book introduces an example logic program that reasons about relationships in a family.
The program starts out by defining some facts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
We can translate this into Clojure Core.Logic fairly directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
At this point we can ask the program to recite the facts back to us. For example, who are Bob’s parents?
In prolog:
1
|
|
and prolog answers:
1 2 |
|
In Clojure’s Core.Logic:
1 2 |
|
and Core.logic answers:
1
|
|
Things get more interesting when we start adding rules to the program so that the system can start deriving new facts. For example, we can add a rule describing what a mother is:
In prolog:
1 2 3 |
|
This reads: “X is the mother of Y if X is the parent of Y and X is female.”
In Core.logic:
1 2 3 4 |
|
Now we can ask who Bob’s mother is:
1 2 3 4 5 |
|
Or, we can move the variable and ask we has Pam as a mother:
1 2 3 4 5 |
|
I’ll provide the rest of the Core.logic version of the family program without much explanation. The important things you need to know to understand this code are:
- run* executes a logic program for all of its solutions
- fresh creates new lexically scoped, unbound variables. All clauses inside fresh are logically AND’d together
- conde similar to an OR. Each conde clause is asserted independently of the others.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
The final rule is especially interesting because it introduces logical recursion. Notice that predecessor is defined in terms of itself.
Now some examples of what this program can derive:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Now, go forth and build your family tree! Or, better yet, go solve Daily Programmer #65 without building a graph!
EDIT: There was a typo in my first example. Thank you zerokarmaleft for pointing it out.