From d61b355413c6f9a0c4d7a1544d7082626bb7b72e Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 23:27:16 +0000 Subject: [PATCH] =?UTF-8?q?mk:=20phase=205B=20=E2=80=94=20project,=20escap?= =?UTF-8?q?e=20into=20host=20SX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (project (vars ...) goal ...) defmacro walks each named var via mk-walk*, rebinds them in the body's lexical scope, then mk-conjs the body goals on the same substitution. Hygienic — gensym'd s-param so user vars survive. Lets you reach into host SX for arithmetic, string ops, anything that needs a ground value: (project (n) (== q (* n n))), (project (s) (== q (str s \"!\"))), and so on. 6 new tests, 194/194 cumulative. --- lib/minikanren/project.sx | 25 ++++++++++++++ lib/minikanren/tests/project.sx | 60 +++++++++++++++++++++++++++++++++ plans/minikanren-on-sx.md | 9 +++-- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 lib/minikanren/project.sx create mode 100644 lib/minikanren/tests/project.sx diff --git a/lib/minikanren/project.sx b/lib/minikanren/project.sx new file mode 100644 index 00000000..476b18f7 --- /dev/null +++ b/lib/minikanren/project.sx @@ -0,0 +1,25 @@ +;; lib/minikanren/project.sx — Phase 5 piece B: `project`. +;; +;; (project (x y) g1 g2 ...) +;; — rebinds each named var to (mk-walk* var s) within the body's +;; lexical scope, then runs the conjunction of the body goals on +;; the same substitution. Use to escape into regular SX (arithmetic, +;; string ops, host predicates) when you need a ground value. +;; +;; If any of the projected vars is still unbound at this point, the body +;; sees the raw `(:var NAME)` term — that is intentional and lets you +;; mix project with `(== ground? var)` patterns or with conda guards. +;; +;; Hygiene: substitution parameter is gensym'd so it doesn't capture user +;; vars (`s` is a popular relation parameter name). + +(defmacro + project + (vars &rest goals) + (let + ((s-sym (gensym "proj-s-"))) + (quasiquote + (fn + ((unquote s-sym)) + ((let (unquote (map (fn (v) (list v (list (quote mk-walk*) v s-sym))) vars)) (mk-conj (splice-unquote goals))) + (unquote s-sym)))))) diff --git a/lib/minikanren/tests/project.sx b/lib/minikanren/tests/project.sx new file mode 100644 index 00000000..b58c71ab --- /dev/null +++ b/lib/minikanren/tests/project.sx @@ -0,0 +1,60 @@ +;; lib/minikanren/tests/project.sx — Phase 5 piece B tests for `project`. + +;; --- project rebinds vars to ground values for SX use --- + +(mk-test + "project-square-via-host" + (run* q (fresh (n) (== n 5) (project (n) (== q (* n n))))) + (list 25)) + +(mk-test + "project-multi-vars" + (run* + q + (fresh + (a b) + (== a 3) + (== b 4) + (project (a b) (== q (+ a b))))) + (list 7)) + +(mk-test + "project-with-string-host-op" + (run* q (fresh (s) (== s "hello") (project (s) (== q (str s "!"))))) + (list "hello!")) + +;; --- project nested inside conde --- + +(mk-test + "project-inside-conde" + (run* + q + (fresh + (n) + (conde ((== n 3)) ((== n 4))) + (project (n) (== q (* n 10))))) + (list 30 40)) + +;; --- project body can be multiple goals (mk-conj'd) --- + +(mk-test + "project-multi-goal-body" + (run* + q + (fresh + (n) + (== n 7) + (project (n) (== q (+ n 1)) (== q (+ n 1))))) + (list 8)) + +(mk-test + "project-multi-goal-body-conflict" + (run* + q + (fresh + (n) + (== n 7) + (project (n) (== q (+ n 1)) (== q (+ n 2))))) + (list)) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index 81c80da9..e3de93d6 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -120,8 +120,9 @@ Key semantic mappings: `tests/relations.sx`; reverseo/flatteno/permuteo/lengtho deferred) ### Phase 5 — `project` + `matche` + negation -- [ ] `project` `(x ...) body` — access reified values of logic vars inside a goal; - escapes to ground values for arithmetic or string ops +- [x] `project` `(x ...) body` — defmacro: rebinds named vars to `(mk-walk* var s)` + in the body's lexical scope, then runs `(mk-conj body...)` on the same + substitution. Hygienic via gensym'd `s`-param. (`Phase 5 piece B`) - [ ] `matche` — pattern matching over logic terms (extension from core.logic) `(matche l ((head . tail) goal) (() goal))` - [x] `conda` — soft-cut: first non-failing head wins; ALL of head's answers @@ -156,6 +157,10 @@ _(none yet)_ _Newest first._ +- **2026-05-07** — **Phase 5 piece B — project**: `lib/minikanren/project.sx` — + defmacro that walks each named var, rebinds them, and runs the body's mk-conj. + Demonstrated escape into host arithmetic / string ops (`(* n n)`, `(str s "!")`). + Hygienic gensym'd s-param. 6 new tests, 194/194 cumulative. - **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)