From ada405b37b47e7b406e15e90ac71527192614361 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 11:27:49 +0000 Subject: [PATCH] =?UTF-8?q?mk:=20defrel=20=E2=80=94=20Prolog-style=20relat?= =?UTF-8?q?ion-definition=20macro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (defrel (NAME ARGS...) (CLAUSE1 ...) (CLAUSE2 ...) ...) expands to (define NAME (fn (ARGS...) (conde (CLAUSE1 ...) (CLAUSE2 ...) ...))). Mirrors Prolog's `name(Args) :- goals.` shape. Inherits the Zzz-on-each- clause laziness from conde, so user relations defined via defrel terminate on partial answers without needing manual delay. Tests redefine membero / listo / pluso through defrel and verify equivalence. 3 new tests, 347/347 cumulative. --- lib/minikanren/defrel.sx | 25 +++++++++++++++++++++ lib/minikanren/tests/defrel.sx | 40 ++++++++++++++++++++++++++++++++++ plans/minikanren-on-sx.md | 5 +++++ 3 files changed, 70 insertions(+) create mode 100644 lib/minikanren/defrel.sx create mode 100644 lib/minikanren/tests/defrel.sx diff --git a/lib/minikanren/defrel.sx b/lib/minikanren/defrel.sx new file mode 100644 index 00000000..03604542 --- /dev/null +++ b/lib/minikanren/defrel.sx @@ -0,0 +1,25 @@ +;; lib/minikanren/defrel.sx — Prolog-style defrel macro. +;; +;; (defrel (NAME ARG1 ARG2 ...) +;; (CLAUSE1 ...) +;; (CLAUSE2 ...) +;; ...) +;; +;; expands to +;; +;; (define NAME (fn (ARG1 ARG2 ...) (conde (CLAUSE1 ...) (CLAUSE2 ...)))) +;; +;; This puts each clause's goals immediately after the head, mirroring +;; Prolog's `name(Args) :- goals.` shape. Clauses are conde-conjoined +;; goals — `Zzz`-wrapping is automatic via `conde`, so recursive +;; relations terminate on partial answers. + +(defmacro + defrel + (head &rest clauses) + (let + ((name (first head)) (args (rest head))) + (list + (quote define) + name + (list (quote fn) args (cons (quote conde) clauses))))) diff --git a/lib/minikanren/tests/defrel.sx b/lib/minikanren/tests/defrel.sx new file mode 100644 index 00000000..9456bde7 --- /dev/null +++ b/lib/minikanren/tests/defrel.sx @@ -0,0 +1,40 @@ +;; lib/minikanren/tests/defrel.sx — Prolog-style relation definition macro. + +(defrel + (my-membero x l) + ((fresh (d) (conso x d l))) + ((fresh (a d) (conso a d l) (my-membero x d)))) + +(mk-test + "defrel-defines-membero" + (run* q (my-membero q (list 1 2 3))) + (list 1 2 3)) + +(defrel + (my-listo l) + ((nullo l)) + ((fresh (a d) (conso a d l) (my-listo d)))) + +(mk-test + "defrel-listo-bounded" + (run 3 q (my-listo q)) + (list + (list) + (list (make-symbol "_.0")) + (list (make-symbol "_.0") (make-symbol "_.1")))) + +;; Multi-arg relation with arithmetic. + +(defrel + (my-pluso a b c) + ((== a :z) (== b c)) + ((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (my-pluso a-1 b c-1)))) + +(mk-test + "defrel-pluso-2-3" + (run* + q + (my-pluso (list :s (list :s :z)) (list :s (list :s (list :s :z))) q)) + (list (list :s (list :s (list :s (list :s (list :s :z))))))) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index 3515133f..56caa71c 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -173,6 +173,11 @@ _(none yet)_ _Newest first._ +- **2026-05-08** — **defrel — Prolog-style relation definition macro**: + `(defrel (NAME ARGS...) (CLAUSE1 ...) (CLAUSE2 ...) ...)` expands to + `(define NAME (fn (ARGS...) (conde (CLAUSE1 ...) (CLAUSE2 ...) ...)))`. + Mirrors Prolog's clause syntax and inherits Zzz-via-conde so recursive + relations terminate. 3 new tests, 347/347 cumulative. - **2026-05-08** — **lasto / init-o**: classic head/tail-style list relations. lasto extracts the final element; init-o extracts everything-but-the-last. Together with appendo, the round-trip `init ⊕ (last) = l` holds. 8 new