~ / track C / clojure ecosystem

core.logic and logic programming

Advanced

core.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.logic is 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 appendo works 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.)

 status: new