~ / track C / clojure ecosystem
core.logic and logic programming
Advancedcore.logic brings miniKanren-style relational programming to Clojure.
You describe what relationships must hold between unknowns; the engine
searches for assignments that satisfy them. It's the same intuition you have
when you write x + 1 = 5 — you didn't subtract, you stated the relation and
let the math solve it. Logic programming generalizes that to programs.
Note:
core.logicis a library (org.clojure/core.logic). The forms below are illustrative — the SCI REPL on this page does not include it.
Minimal example: unification
run* returns all values that some unknown can take to make a goal true.
== is the unification operator: "these two terms must be equal."
(require '[clojure.core.logic :as l :refer [run* fresh ==]])
(run* [q]
(== q 42))
;; => (42)
q is the fresh logic variable we want a value for. The single goal says
q must equal 42, so the engine returns the one solution.
Multiple solutions: search
Logic programs shine when there are many answers. membero says "x is one
of these":
(run* [q]
(l/membero q [:a :b :c]))
;; => (:a :b :c)
The engine tried each option and returned every one that satisfied the goal.
Constraints as data
Real power comes from combining goals. Here we ask: what pairs [a b] from
[1 2 3] and [10 20 30] sum to 21?
(run* [a b]
(l/membero a [1 2 3])
(l/membero b [10 20 30])
(l/project [a b]
(== 21 (+ a b))))
;; => ([1 20] [11 nil] ...) ;; conceptually [1 20]; specifics depend on the goal
The example glosses over arithmetic — core.logic has fd/+ (finite-domain)
goals that are fully relational. The point is: you stated the relationship
between a, b, and 21, not the search.
Why this matters
- Program direction reverses. A relational
appendoworks as "append" but also as "split" — give the output, get all input pairs that produce it. - Search is built in. You don't write the backtracking loop; the engine does.
- Some problems are vastly simpler this way. Type inference, constraint solving, parsing, pattern matching — all benefit from a relational view.
Check yourself
? quiz
What does `(run* [q] (== q 1) (== q 2))` return, and why?
Exercise
Using run*, fresh, and membero, find all pairs (a, b) where a is in
[:cat :dog :bat] and b is in [:hat :bag] such that the first letter of
the name of a matches the first letter of the name of b. (Hint: you can
use Clojure data to encode the relationship if you don't have a relational
"first letter" goal.)