This document summarizes core.logic, a relational logic programming library for Clojure. It provides examples of core.logic concepts like unification, conde, fresh, membero, distincto, everyg, lvar, finite domains, and using core.logic to solve logic puzzles like map coloring, rock paper scissors, cryptarithmetic, and sudoku. Core.logic allows defining relations and facts to constrain logic variables and find all solutions that satisfy the goals.
3. core.logic in the real world
ThreatGRID uses core.logic to
process observations of malware
execution looking for behavioral
indicators of compromise.
Observations are simple factual statements that map into
core.logic relations and SQL tables. The mapping allows
the same data model to be used for core.logic programs in
memory, as well as persistance in a database and later
analysis and querying via SQL.
A persistent core.logic database. pldb provides an in-
memory persistent fact database making it easier to use
core.logic in multi-threaded environments like web
applications.
github.com/threatgrid/observations
github.com/threatgrid/pldb
4. run*
core.logic
(run* [q])
This is the simplest possible
core.logic program. q is the
query. run* says âgive me all the
resultsâ.
run* returns a seq of all possible
results. The symbol _0
represents a fresh (unbound)
logic variable.
(_0)
5. unification
core.logic
(run* [q]
(== q :hello-world))
The most fundamental operation
on a logic variable is to unify it.
uniïŹcation is ==.
There is only one value of q that
satisïŹes the relation. (:hello-world)
6. unification
core.logic
(run* [q]
(== q [:hello :world]))
Logic variables can also be
uniïŹed over sequences.
There is still only one value of q
that satisïŹes the relation.
([:hello :world])
7. unification
core.logic
(run* [q]
(== q [:hello :world])
(== q [:hello :world]))
A logic variable can be uniïŹed
with the same value multiple
times.
([:hello :world])
8. unification
core.logic
(run* [q]
(== q :hello)
(== q :world))
A logic variable cannot unify with
two diïŹerent values at the same
time.
There are no values of q that
satisfy the relation. ()
9. conde
core.logic
(run* [q]
(conde
[(== q :hello)]
[(== q :world)]))
You can introduce alternative
values with conde. Every conde
line that succeeds produces
possible alternative values.
There are 2 values of q that
satisfy the relation. (:hello :world)
10. Disunification
core.logic
(run* [q]
(conde
[(== q :hello)]
[(== q :world)])
(!= q :hello))
!= introduces a constraint that
two values never unify.
There are 2 values of q that
satisfy the conde goal, but !=
eliminates one of them. (:world)
11. FRESH
core.logic
(run* [q]
(fresh [x y]
(== x :something)
(== y :something-else)))
fresh introduces new logic
variables.
x and y are bound, but the query
remains unbound.
(_0)
12. FRESH
core.logic
(run* [q]
(fresh [x y]
(== x :something)
(== x :something-else)))
The query fails since no value of
q can make x unify with two
diïŹerent values.
()
13. FRESH
core.logic
(run* [q]
(fresh [x y]
(== q [x :and y])
(== x :something)
(== :something-else y)))
Order does not matter for
uniïŹcation.
([:something :and :something-else])
14. membero
core.logiccore.logic
(run* [q]
(fresh [smurf]
(membero smurf
[:papa :brainy :lazy :handy])
(== q [smurf smurf])))
membero is relation that
succeeds when the ïŹrst
argument is a member of the
second argument. It can
succeed multiple times.
q produces each success ([:papa :papa]
[:brainy :brainy]
[:lazy :lazy]
[:handy :handy])
16. distincto
core.logiccore.logic
(run* [q]
(fresh [smurf1 smurf2 smurf3]
(membero smurf1
[:papa :brainy :lazy :handy])
(membero smurf2
[:papa :brainy :lazy :handy])
(membero smurf3
[:papa :brainy :lazy :handy])
(distincto [smurf1 smurf2 smurf3])
(== q [smurf1 smurf2 smurf3])))
distincto ensures that no two
items in the relation unify with
each other. smurf1 will never
unify with smurf2, and neither will
unify with smurf3.
([:papa :brainy :lazy]
[:papa :brainy :handy]
[:brainy :papa :lazy]
[:brainy :papa :handy]
...
[:handy :lazy :brainy])
17. everyg
core.logiccore.logic
(run* [q]
(fresh [smurf1 smurf2 smurf3]
(== q [smurf1 smurf2 smurf3])
(everyg #(membero % [:papa :brainy :lazy :handy])
q)
(distincto q)))
everyg ensures that every
element in a collection satisïŹes a
goal. It is not a proper relation, in
that it requires the collection to
already be a seq.
([:papa :brainy :lazy]
[:papa :brainy :handy]
[:brainy :papa :lazy]
[:brainy :papa :handy]
...
[:handy :lazy :brainy])
18. lvar
core.logiccore.logic
(run* [q]
(== q [(lvar) (lvar) (lvar)])
(everyg #(membero % [:papa :brainy :lazy :handy])
q)
(distincto q))
lvar creates a new a logic
variable. Since we donât need to
refer to the items individually, we
can just say that the
([:papa :brainy :lazy]
[:papa :brainy :handy]
[:brainy :papa :lazy]
[:brainy :papa :handy]
...
[:handy :lazy :brainy])
19. Map coloring
core.logiccore.logic
http://pragprog.com/book/btlang/seven-languages-in-seven-weeks
(run 1 [q]
(fresh [tn ms al ga fl]
(everyg #(membero % [:red :blue :green])
[tn ms al ga fl])
(!= ms tn) (!= ms al) (!= al tn)
(!= al ga) (!= al fl) (!= ga fl) (!= ga tn)
Â
(== q {:tennesse tn
:mississipi ms
:alabama al
:georgia ga
:florida fl})))
({:tennesse :blue,
:mississipi :red,
:alabama :green,
:georgia :red,
:florida :blue})
20. rock paper scissors
core.logiccore.logic
(defn beatso [player1 player2]
(conde
[(== player1 :rock) (== player2 :scissors)]
[(== player1 :scissors) (== player2 :paper)]
[(== player1 :paper) (== player2 :rock)]))
beatso is a custom relation
between two terms. It succeeds
when the ïŹrst players move
beats the second players move
24. rock paper scissors
core.logiccore.logic
(run* [q]
(fresh [x y]
(beatso x y)
(== q [x y])))
This query asks for all the pairs
where x beats y.
([:rock :scissors]
[:scissors :paper]
[:paper :rock])
25. FACTS and RELATIONS
core.logiccore.logic
rpsls is a relation of one term.
Five facts are asserted about the
relation.
(defrel rpsls gesture)
(fact rpsls :rock)
(fact rpsls :paper)
(fact rpsls :scissors)
(fact rpsls :lizard)
(fact rpsls :spock)
26. FACTS and RELATIONS
core.logiccore.logic
(run* [q]
(rpsls q))
defrel relations answer queries in
the same way as the other
relations weâve seen.
(:rock :paper :scissors :lizard :spock)
27. FACTS and RELATIONS
core.logiccore.logic
beats is a relation of two terms,
indicating the ïŹrst gesture beats
the second one.
(defrel beats gesture1 gesture2)
(fact beats :scissors :paper)
(fact beats :paper :rock)
(fact beats :rock :lizard)
(fact beats :lizard :spock)
(fact beats :spock :scissors)
(fact beats :scissors :lizard)
(fact beats :lizard :paper)
(fact beats :paper :spock)
(fact beats :spock :rock)
(fact beats :rock :scissors)
28. FACTS and RELATIONS
core.logiccore.logic
(run* [q]
(fresh [x y]
(beats :spock x)
(beats x y)
(beats y :spock)
(== q [:spock x y :spock])))
We can ask questions like: give
me a 4-chain of dominated
moves starting and ending
with :spock. There are three
solutions.
([:spock :scissors :lizard :spock]
[:spock :scissors :paper :spock]
[:spock :rock :lizard :spock])
29. FACTS and RELATIONS
core.logiccore.logic
(defn win-chaino [x]
(fresh [a d]
(rpsls a)
(conso a d x)
(conde
[(emptyo d)]
[(fresh [b]
(beats a b)
(firsto d b))
(win-chaino d)])))
A winning chain is a single rpsls
move either by itself or followed
by a winning chain whose ïŹrst
move is beaten by the original
move.
conso, emptyo and ïŹrsto are
relations over cons lists.
31. USEless logic puzzle
core.logiccore.logic
⣠petey pig did not hand out the popcorn
⣠pippin pig does not live in the wood house
⣠the pig that lives in the straw house handed out popcorn
⣠Petunia pig handed out apples
⣠The pig who handed out chocolate does not live in the brick house.
Three little pigs, who each
lived in a diïŹerent type of
house, handed out treats for
Halloween. Use the clues to
ïŹgure out which pig lived in
each house, and what type of
treat each pig handed out.
http://holidays.hobbyloco.com/halloween/logic1.html
32. USEless logic puzzle
core.logiccore.logic
(defn pigso [q]
(fresh [h1 h2 h3 t1 t2 t3]
(== q [[:petey h1 t1]
[:pippin h2 t2]
[:petunia h3 t3]])
(permuteo [t1 t2 t3]
[:chocolate :popcorn :apple])
(permuteo [h1 h2 h3]
[:wood :straw :brick])
 ... ))
pigso starts by deïŹning the
solution space.
permuteo succeeds when the
ïŹrst list is permutation of the
second.
33. USEless logic puzzle
core.logiccore.logic
(fresh [notpopcorn _]
(membero notpopcorn [:chocolate :apple])
(membero [:petey _ notpopcorn] q))
(fresh [notwood _]
(membero notwood [:straw :brick])
(membero [:pippin notwood _] q))
(fresh [_]
(membero [_ :straw :popcorn] q))
(fresh [_]
(membero [:petunia _ :apple] q))
(fresh [notbrick _]
(membero notbrick [:straw :wood])
(membero [_ notbrick :chocolate] q))
The clues translate cleanly to
goals constraining the solution
space.
membero has a solution when
the ïŹrst item is a member of the
second.
35. FINITE DOMAINS
core.logiccore.logic
fd/interval declares a ïŹnite
integer interval and fd/in
contrains logic variables to a
domain.
(defn two-plus-two-is-four [q]
(fresh [t w o f u r TWO FOUR]
(fd/in t w o f u r (fd/interval 0 9))
(fd/distinct [t w o f u r])
(fd/in TWO (fd/interval 100 999))
(fd/in FOUR (fd/interval 1000 9999))
Â
...
(== q [TWO TWO FOUR])))
T W O
+ T W O
-------
F O U R
http://www.amazon.com/Crypt-arithmetic-Puzzles-in-PROLOG-ebook/dp/B006X9LY8O
36. FINITE DOMAINS
core.logiccore.logic
fd/eq translates simple math to
constraints over ïŹnite domain
logic variables.
(fd/eq (= TWO
(+ (* 100 t)
(* 10 w)
o)))
(fd/eq (= FOUR
(+ (* 1000 f)
(* 100 o)
(* 10 u)
r)))
Â
(fd/eq (= (+ TWO TWO) FOUR))
T W O
+ T W O
-------
F O U R
37. FINITE DOMAINS
core.logiccore.logic
There are 7 unique solutions to
the problem.
(run* [q]
(two-plus-two-is-four q))
T W O
+ T W O
-------
F O U R
([734 734 1468]
[765 765 1530]
[836 836 1672]
[846 846 1692]
[867 867 1734]
[928 928 1856]
[938 938 1876])
38. sudoku made easier
core.logiccore.logic
After setting up the logic
variables and initializing state,
the solution simply requires every
row, column and square on the
board to have distinct values.
(defn solve [puzzle]
(let [sd-num (fd/domain 1 2 3 4 5 6 7 8 9)
board (repeatedly 81 lvar)
rows (into [] (map vec (partition 9 board)))
cols (apply map vector rows)
squares (for [x (range 0 9 3)
y (range 0 9 3)]
(get-square rows x y))]
Â
(run* [q]
(== q board)
(everyg #(fd/in % sd-num) board)
(init-board board puzzle)
(everyg fd/distinct rows)
(everyg fd/distinct cols)
(everyg fd/distinct squares))))
https://gist.github.com/swannodette/3217582
39. sudoku made easier
core.logiccore.logic
matche is pattern matching
syntax for conde. To unify the
initial logic variables with a
board, we require either the
board have a 0 or that the logic
variable uniïŹes with the value of
the board.
(defn init-board [vars puzzle]
(matche [vars puzzle]
([[] []]
succeed)
([[_ . vs] [0 . ps]]
(init-board vs ps))
([[num . vs] [num . ps]]
(init-board vs ps))))