diff --git a/lib/minikanren/peano.sx b/lib/minikanren/peano.sx new file mode 100644 index 00000000..9865cf72 --- /dev/null +++ b/lib/minikanren/peano.sx @@ -0,0 +1,35 @@ +;; lib/minikanren/peano.sx — Peano-encoded natural-number relations. +;; +;; Same encoding as `lengtho`: zero is the keyword `:z`; successors are +;; `(:s n)`. So 3 = `(:s (:s (:s :z)))`. `(:z)` and `(:s ...)` are normal +;; SX values that unify positionally — no special primitives needed. +;; +;; Peano arithmetic is the canonical miniKanren way to test addition / +;; multiplication / less-than relationally without an FD constraint store. +;; (CLP(FD) integers come in Phase 6.) + +(define zeroo (fn (n) (== n :z))) + +(define succ-of (fn (n m) (== m (list :s n)))) + +(define + pluso + (fn + (a b c) + (conde + ((== a :z) (== b c)) + ((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (pluso a-1 b c-1)))))) + +(define minuso (fn (a b c) (pluso b c a))) + +(define lteo (fn (a b) (fresh (k) (pluso a k b)))) + +(define lto (fn (a b) (fresh (sa) (succ-of a sa) (lteo sa b)))) + +(define + *o + (fn + (a b c) + (conde + ((== a :z) (== c :z)) + ((fresh (a-1 ab-1) (== a (list :s a-1)) (*o a-1 b ab-1) (pluso b ab-1 c)))))) diff --git a/lib/minikanren/tests/peano.sx b/lib/minikanren/tests/peano.sx new file mode 100644 index 00000000..682a3dd4 --- /dev/null +++ b/lib/minikanren/tests/peano.sx @@ -0,0 +1,119 @@ +;; lib/minikanren/tests/peano.sx — Peano arithmetic. +;; +;; Builds Peano numbers via a host-side helper so tests stay readable. +;; (mk-nat 3) → (:s (:s (:s :z))). + +(define + mk-nat + (fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1)))))) + +;; --- zeroo --- + +(mk-test + "zeroo-zero-succeeds" + (run* q (zeroo :z)) + (list (make-symbol "_.0"))) +(mk-test + "zeroo-non-zero-fails" + (run* q (zeroo (mk-nat 1))) + (list)) + +;; --- pluso forward --- + +(mk-test + "pluso-forward-2-3" + (run* q (pluso (mk-nat 2) (mk-nat 3) q)) + (list (mk-nat 5))) + +(mk-test "pluso-forward-zero-zero" (run* q (pluso :z :z q)) (list :z)) + +(mk-test + "pluso-forward-zero-n" + (run* q (pluso :z (mk-nat 4) q)) + (list (mk-nat 4))) + +(mk-test + "pluso-forward-n-zero" + (run* q (pluso (mk-nat 4) :z q)) + (list (mk-nat 4))) + +;; --- pluso backward --- + +(mk-test + "pluso-recover-augend" + (run* q (pluso q (mk-nat 2) (mk-nat 5))) + (list (mk-nat 3))) + +(mk-test + "pluso-recover-addend" + (run* q (pluso (mk-nat 2) q (mk-nat 5))) + (list (mk-nat 3))) + +(mk-test + "pluso-enumerate-pairs-summing-to-3" + (run* + q + (fresh (a b) (pluso a b (mk-nat 3)) (== q (list a b)))) + (list + (list :z (mk-nat 3)) + (list (mk-nat 1) (mk-nat 2)) + (list (mk-nat 2) (mk-nat 1)) + (list (mk-nat 3) :z))) + +;; --- minuso --- + +(mk-test + "minuso-5-2-3" + (run* q (minuso (mk-nat 5) (mk-nat 2) q)) + (list (mk-nat 3))) + +(mk-test + "minuso-n-n-zero" + (run* q (minuso (mk-nat 7) (mk-nat 7) q)) + (list :z)) + +;; --- *o --- + +(mk-test + "times-2-3" + (run* q (*o (mk-nat 2) (mk-nat 3) q)) + (list (mk-nat 6))) + +(mk-test + "times-zero-anything-zero" + (run* q (*o :z (mk-nat 99) q)) + (list :z)) + +(mk-test + "times-3-4" + (run* q (*o (mk-nat 3) (mk-nat 4) q)) + (list (mk-nat 12))) + +;; --- lteo / lto --- + +(mk-test + "lteo-success" + (run 1 q (lteo (mk-nat 2) (mk-nat 5))) + (list (make-symbol "_.0"))) + +(mk-test + "lteo-equal-success" + (run 1 q (lteo (mk-nat 3) (mk-nat 3))) + (list (make-symbol "_.0"))) + +(mk-test + "lteo-greater-fails" + (run* q (lteo (mk-nat 5) (mk-nat 2))) + (list)) + +(mk-test + "lto-strict-success" + (run 1 q (lto (mk-nat 2) (mk-nat 5))) + (list (make-symbol "_.0"))) + +(mk-test + "lto-equal-fails" + (run* q (lto (mk-nat 3) (mk-nat 3))) + (list)) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index aa332a34..81c80da9 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -156,6 +156,11 @@ _(none yet)_ _Newest first._ +- **2026-05-07** — **Peano arithmetic** (`lib/minikanren/peano.sx`): zeroo, pluso, + minuso, lteo, lto, *o on Peano-encoded naturals (`:z` / `(:s n)`). pluso runs + forward, backward, and enumerates: `(run* q (fresh (a b) (pluso a b 3) + (== q (list a b))))` → all 4 pairs summing to 3. *o uses repeated pluso — + works for small inputs, slower for larger. 19 new tests, 188/188 cumulative. - **2026-05-07** — **Phase 5 piece A — conda**: soft-cut. Mirrors `condu` minus the `onceo` on the head: all head answers are conjuncted through the rest of the chosen clause. 7 new tests including the conda-vs-condu divergence test.