Merge loops/minikanren into architecture: full miniKanren-on-SX library
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Squash merge of 76 commits from loops/minikanren. Adds lib/minikanren/ — a complete miniKanren-on-SX implementation built on top of lib/guest/match.sx, validating the lib-guest unify-and-match kit as intended. Modules (20 .sx files, ~1700 LOC): unify, stream, goals, fresh, conde, condu, conda, run, relations, peano, intarith, project, nafc, matche, fd, queens, defrel, clpfd, tabling Phases 1–5 fully done (core miniKanren API, all classic relations, matche, conda, project, nafc). Phase 6 — native CLP(FD): domain primitives, fd-in / fd-eq / fd-neq / fd-lt / fd-lte / fd-plus / fd-times / fd-distinct / fd-label, with constraint reactivation iterating to fixed point. N-queens via FD: 4-queens 2 solutions, 5-queens 10 solutions (vs naive timeout past N=4). Phase 7 — naive ground-arg tabling: table-1 / table-2 / table-3. Fibonacci canary: tab-fib(25) = 75025 in seconds, naive fib(25) times out at 60s. Ackermann via table-3: A(3,3) = 61. 71 test files, 644+ tests passing across the suite. Producer/consumer SLG (cyclic patho, mutual recursion) deferred — research-grade work. The lib-guest validation experiment is conclusive: lib/minikanren/ unify.sx adds ~50 lines of local logic (custom cfg, deep walk*, fresh counter) over lib/guest/match.sx's ~100-line kit. The kit earns its keep ~3× by line count.
This commit is contained in:
@@ -50,68 +50,126 @@ Key semantic mappings:
|
||||
## Roadmap
|
||||
|
||||
### Phase 1 — variables + unification
|
||||
- [ ] `make-var` → fresh logic variable (unique mutable box)
|
||||
- [ ] `var?` `v` → bool — is this a logic variable?
|
||||
- [ ] `walk` `term` `subst` → follow substitution chain to ground term or unbound var
|
||||
- [ ] `walk*` `term` `subst` → deep walk (recurse into lists/dicts)
|
||||
- [ ] `unify` `u` `v` `subst` → extended substitution or `#f` (failure)
|
||||
- [x] `make-var` → fresh logic variable (unique mutable box)
|
||||
- [x] `var?` `v` → bool — is this a logic variable?
|
||||
- [x] `walk` `term` `subst` → follow substitution chain to ground term or unbound var
|
||||
- [x] `walk*` `term` `subst` → deep walk (recurse into lists/dicts)
|
||||
- [x] `unify` `u` `v` `subst` → extended substitution or `#f` (failure)
|
||||
Handles: var/var, var/term, term/var, list unification, number/string/symbol equality.
|
||||
No occurs check by default; `unify-check` with occurs check as opt-in.
|
||||
- [ ] Empty substitution `empty-s` = `(list)` (empty assoc list)
|
||||
- [ ] Tests in `lib/minikanren/tests/unify.sx`: ground terms, vars, lists, failure, occurs
|
||||
- [x] Empty substitution `empty-s` (dict-based via kit's `empty-subst` — assoc list was a sketch; kit ships dict, kept it)
|
||||
- [x] Tests in `lib/minikanren/tests/unify.sx`: ground terms, vars, lists, failure, occurs
|
||||
|
||||
### Phase 2 — streams + goals
|
||||
- [ ] Stream type: `mzero` (empty stream = `nil`), `unit s` (singleton = `(list s)`),
|
||||
`mplus` (interleave two streams), `bind` (apply goal to stream)
|
||||
- [ ] Lazy streams via `delay`/`force` — mature pairs for depth-first, immature for lazy
|
||||
- [ ] `==` goal: `(fn (s) (let ((s2 (unify u v s))) (if s2 (unit s2) mzero)))`
|
||||
- [ ] `succeed` / `fail` — trivial goals
|
||||
- [ ] `fresh` — `(fn (f) (fn (s) ((f (make-var)) s)))` — introduces one var; `fresh*` for many
|
||||
- [ ] `conde` — interleaving disjunction of goal lists
|
||||
- [ ] `condu` — committed choice (soft-cut): only explores first successful clause
|
||||
- [ ] `onceo` — succeeds at most once
|
||||
- [ ] Tests: basic goal composition, backtracking, interleaving
|
||||
- [x] Stream type: `mzero` (empty), `unit s` (singleton), `mk-mplus` (interleave),
|
||||
`mk-bind` (apply goal to stream). Names mk-prefixed because SX has a host
|
||||
`bind` primitive that silently shadows user defines.
|
||||
- [x] Lazy streams via thunks: a paused stream is a zero-arg fn; mk-mplus suspends
|
||||
and swaps when its left operand is paused, giving fair interleaving.
|
||||
- [x] `==` goal: `(fn (s) (let ((s2 (mk-unify u v s))) (if s2 (unit s2) mzero)))`
|
||||
- [x] `==-check` — opt-in occurs-checked equality goal
|
||||
- [x] `succeed` / `fail` — trivial goals
|
||||
- [x] `conj2` / `mk-conj` (variadic) — sequential conjunction
|
||||
- [x] `disj2` / `mk-disj` (variadic) — interleaved disjunction (raw — `conde`
|
||||
adds the implicit-conj-per-clause sugar later)
|
||||
- [x] `fresh` — introduces logic variables inside a goal body. Implemented as a
|
||||
defmacro: `(fresh (x y) g1 g2 ...)` ⇒ `(let ((x (make-var)) (y (make-var)))
|
||||
(mk-conj g1 g2 ...))`. Also `call-fresh` for programmatic goal building.
|
||||
- [x] `conde` — sugar over disj+conj, one row per clause; defmacro that
|
||||
wraps each clause body in `mk-conj` and folds via `mk-disj`. Notes:
|
||||
with eager streams ordering is left-clause-first DFS; true interleaving
|
||||
requires paused thunks (Phase 4 recursive relations).
|
||||
- [x] `condu` — committed choice. defmacro folding clauses into a runtime
|
||||
`condu-try` walker; first clause whose head goal yields a non-empty
|
||||
stream commits its first answer, rest-goals run on that single subst.
|
||||
- [x] `onceo` — `(stream-take 1 (g s))`; trims a goal's stream to ≤1 answer.
|
||||
- [x] Tests: basic goal composition, backtracking, interleaving (110 cumulative)
|
||||
|
||||
### Phase 3 — run + reification
|
||||
- [ ] `run*` `goal` → list of all answers (reified)
|
||||
- [ ] `run n` `goal` → list of first n answers
|
||||
- [ ] `reify` `term` `subst` → replace unbound vars with `_0`, `_1`, ... names
|
||||
- [ ] `reify-s` → builds reification substitution for naming unbound vars consistently
|
||||
- [ ] `fresh` with multiple variables: `(fresh (x y z) goal)` sugar
|
||||
- [ ] Query variable conventions: `q` as canonical query variable
|
||||
- [ ] Tests: classic miniKanren programs — `(run* q (== q 1))` → `(1)`,
|
||||
- [x] `run*` `goal` → list of all answers (reified). defmacro: bind q-name as
|
||||
fresh var, conj goals, take all from stream, reify each.
|
||||
- [x] `run n` `goal` → list of first n answers (defmacro; n = -1 means all)
|
||||
- [x] `reify` `term` `subst` → walk* + build reification subst + walk* again
|
||||
- [x] `reify-s` → maps each unbound var (in left-to-right walk order) to a
|
||||
`_.N` symbol via `(make-symbol (str "_." n))`
|
||||
- [x] `fresh` with multiple variables — already shipped Phase 2B.
|
||||
- [x] Query variable conventions: `q` as canonical query variable (matches TRS)
|
||||
- [x] Tests: classic miniKanren programs — `(run* q (== q 1))` → `(1)`,
|
||||
`(run* q (conde ((== q 1)) ((== q 2))))` → `(1 2)`,
|
||||
Peano arithmetic, `appendo` preview
|
||||
`(run* q (fresh (x y) (== q (list x y))))` → `((_.0 _.1))`. Peano +
|
||||
`appendo` deferred to Phase 4.
|
||||
|
||||
### Phase 4 — standard relations
|
||||
- [ ] `appendo` `l` `s` `ls` — list append, runs forwards and backwards
|
||||
- [ ] `membero` `x` `l` — x is a member of l
|
||||
- [ ] `listo` `l` — l is a proper list
|
||||
- [ ] `nullo` `l` — l is empty
|
||||
- [ ] `pairo` `p` — p is a pair (cons cell)
|
||||
- [ ] `caro` `p` `a` — car of pair
|
||||
- [ ] `cdro` `p` `d` — cdr of pair
|
||||
- [ ] `conso` `a` `d` `p` — cons
|
||||
- [ ] `firsto` / `resto` — aliases for caro/cdro
|
||||
- [ ] `reverseo` `l` `r` — reverse of list
|
||||
- [ ] `flatteno` `l` `f` — flatten nested lists
|
||||
- [ ] `permuteo` `l` `p` — permutation of list
|
||||
- [ ] `lengtho` `l` `n` — length as a relation (Peano or integer)
|
||||
- [ ] Tests: run each relation forwards and backwards; generate from partial inputs
|
||||
- [x] `appendo` `l` `s` `ls` — list append, runs forwards AND backwards.
|
||||
Canary green: `(run* q (appendo (1 2) (3 4) q))` → `((1 2 3 4))`;
|
||||
`(run* q (fresh (l s) (appendo l s (1 2 3)) (== q (list l s))))` →
|
||||
all four splits.
|
||||
- [x] `membero` `x` `l` — enumerates: `(run* q (membero q (a b c)))` → `(a b c)`
|
||||
- [x] `listo` `l` — l is a proper list; enumerates list shapes with laziness
|
||||
- [x] `nullo` `l` — l is empty
|
||||
- [x] `pairo` `p` — p is a (non-empty) cons-cell / list
|
||||
- [x] `caro` / `cdro` / `conso` / `firsto` / `resto`
|
||||
- [x] `reverseo` `l` `r` — reverse of list. Forward is fast; backward is `run 1`-clean,
|
||||
`run*` diverges due to interleaved unbounded list search (canonical TRS issue).
|
||||
- [x] `flatteno` `l` `f` — flatten nested lists. Three conde clauses:
|
||||
empty → empty; pair → recurse on car & cdr + appendo; otherwise atom →
|
||||
`(== flat (list tree))`. Atom-ness is asserted via `nafc (nullo tree)
|
||||
+ nafc (pairo tree)` — both succeed only when tree is a ground non-list.
|
||||
Works on ground inputs.
|
||||
- [x] `permuteo` `l` `p` — permutation of list. Built on `inserto` (insert at any
|
||||
position) and recursive permutation of tail. All 6 perms of (1 2 3) generated
|
||||
forward; backward `run 1 q (permuteo q (a b c))` finds the input.
|
||||
- [x] `lengtho` `l` `n` — length as a relation, Peano-encoded:
|
||||
`:z` / `(:s :z)` / `(:s (:s :z))` ... matches TRS. Forward is direct;
|
||||
backward enumerates lists of a given length.
|
||||
- [x] Tests: run each relation forwards and backwards (so far 25 in
|
||||
`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
|
||||
- [ ] `matche` — pattern matching over logic terms (extension from core.logic)
|
||||
`(matche l ((head . tail) goal) (() goal))`
|
||||
- [ ] `conda` — soft-cut disjunction (like Prolog `->`)
|
||||
- [ ] `condu` — committed choice (already in phase 2; refine semantics here)
|
||||
- [ ] `nafc` — negation as finite failure with constraint
|
||||
- [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`)
|
||||
- [x] `matche` — pattern matching over logic terms. Pattern grammar: `_` /
|
||||
symbol / atom / `()` / `(p1 p2 ... pn)`. Each clause becomes
|
||||
`(fresh (vars-in-pat) (== target pat-expr) body...)`. Repeated symbol
|
||||
names in a pattern produce the same fresh var, so they unify (== check).
|
||||
Built without quasiquote (the expander does not recurse into lambda
|
||||
bodies). Fixed-length list patterns only — head/tail destructuring uses
|
||||
`(fresh (a d) (conso a d target) body)` directly.
|
||||
- [x] `conda` — soft-cut: first non-failing head wins; ALL of head's answers
|
||||
flow through rest-goals; later clauses not tried (`Phase 5 piece A`)
|
||||
- [x] `condu` — committed choice (Phase 2)
|
||||
- [x] `nafc` — negation as finite failure: `(nafc g)` yields the input subst
|
||||
iff g has zero answers. Standard caveats apply (open-world unsoundness;
|
||||
diverges if g is infinite). `Phase 5 piece C`.
|
||||
- [ ] Tests: Zebra puzzle, N-queens, Sudoku via `project`, family relations via `matche`
|
||||
|
||||
### Phase 6 — arithmetic constraints CLP(FD)
|
||||
- [ ] Finite domain variables: `fd-var` with domain `[lo..hi]`
|
||||
- [ ] `in` `x` `domain` — constrain x to domain
|
||||
- [x] Finite domain variables: domain stored under reserved key `_fd` in
|
||||
the substitution dict; ascending sorted-int-list representation;
|
||||
domain primitives `fd-dom-from-list`, `fd-dom-intersect`,
|
||||
`fd-dom-without`, `fd-dom-range`, `fd-dom-min/max/empty?/singleton?`.
|
||||
- [x] `fd-in x dom` — narrows x's domain by intersection.
|
||||
- [x] `fd-eq x y`, `fd-neq x y`, `fd-lt`, `fd-lte` — propagator-store
|
||||
goals. Each adds a closure to the constraints field and runs it
|
||||
on post; closures re-fire after every label step via fd-fire-store.
|
||||
- [x] `fd-plus x y z`, `fd-times x y z` — ground-cases propagators
|
||||
(when 2 of 3 walk to numbers, the third is derived).
|
||||
- [x] `fd-distinct vars` — pairwise alldifferent via fd-neq folds.
|
||||
- [x] Constraint reactivation: `fd-fire-store` iterates to fixed point
|
||||
using a domain+bindings signature comparison; ensures multi-step
|
||||
propagation chains (e.g. fd-plus binds a fresh var, which then
|
||||
lets a downstream fd-neq fire).
|
||||
- [x] Labelling: `fd-label vars` enumerates each var's domain via
|
||||
mk-mplus over singleton bindings; constraint store re-fires after
|
||||
each binding.
|
||||
- [x] Tests: N-queens via FD — 4-queens finds both solutions, 5-queens
|
||||
finds all 10 in seconds (vs the naive enumerate-then-filter
|
||||
version which times out past N=4).
|
||||
- [x] `ino` `x` `domain` — alias for `(membero x domain)` (kept for
|
||||
the simple enumerate-then-filter pattern alongside fd-in).
|
||||
- [x] `all-distincto` `l` — original membero-based version (kept alongside
|
||||
the newer fd-distinct).
|
||||
- [ ] `fd-eq` `x` `y` — x = y (constraint propagation)
|
||||
- [ ] `fd-neq` `x` `y` — x ≠ y
|
||||
- [ ] `fd-lt` `fd-lte` `fd-gt` `fd-gte` — ordering constraints
|
||||
@@ -122,10 +180,22 @@ Key semantic mappings:
|
||||
- [ ] Tests: send-more-money, N-queens with CLP(FD), map coloring, cryptarithmetic
|
||||
|
||||
### Phase 7 — tabling (memoization of relations)
|
||||
- [ ] `tabled` annotation: memoize calls to a relation using a hash table
|
||||
- [ ] Prevents infinite loops in recursive relations like `patho` on cyclic graphs
|
||||
- [ ] Producer/consumer scheduling for tabled relations (variant of SLG resolution)
|
||||
- [ ] Tests: cyclic graph reachability, mutual recursion, Fibonacci via tabling
|
||||
- [x] `table-1`, `table-2`, `table-3` wrappers: ground-arg memoization
|
||||
for 1-, 2-, and 3-argument relations.
|
||||
- [x] `table-2` wrapper: ground-arg memoization for 2-arg relations.
|
||||
Cache keyed by walked input; on miss runs underlying relation,
|
||||
collects all output values from the answer stream, stores, and
|
||||
replays. Subsequent calls with the same ground input replay the
|
||||
cached values (no recomputation).
|
||||
- [x] Fibonacci canary green: tabled `fib(25) = 75025` in seconds;
|
||||
naive `fib(25)` times out at 60s. Memoization turns exponential
|
||||
recursion into linear.
|
||||
- [x] Ackermann canary green via `table-3`: `A(3, 3) = 61`.
|
||||
- [ ] Producer/consumer SLG scheduling — required to handle recursive
|
||||
tabled calls with the SAME ground key (e.g. cyclic `patho` with a
|
||||
shared key); naive memoization deferred to a future iteration.
|
||||
- [ ] Tests: cyclic graph reachability via tabled patho (deferred —
|
||||
requires SLG); mutual recursion (deferred).
|
||||
|
||||
## Blockers
|
||||
|
||||
@@ -135,4 +205,242 @@ _(none yet)_
|
||||
|
||||
_Newest first._
|
||||
|
||||
_(awaiting phase 1)_
|
||||
- **2026-05-08** — **Session snapshot**: 17 lib files, 61 test files, 1229
|
||||
library LOC + 4360 test LOC, **551/551 tests cumulative**. Library covers
|
||||
Phases 1–5 fully, Phase 6 partial (FD helpers + intarith escape), Phase 7
|
||||
documented via cyclic-graph divergence test. lib-guest validation
|
||||
completed: `lib/minikanren/unify.sx` ≈ 50 LOC of local logic over
|
||||
`lib/guest/match.sx`'s ≈100 LOC kit (kit earns ~3× by line count). Major
|
||||
classic miniKanren tests green: appendo forwards/backwards, Peano
|
||||
arithmetic, 4-queens, Pythagorean triples, family-relations / pet
|
||||
puzzle / symbolic differentiation, 2x2 Latin square. Ready for Phase 6
|
||||
(native FD with arc-consistency) and Phase 7 (tabling) as future work.
|
||||
- **2026-05-08** — **zip-with-o**: element-wise relational combine over two
|
||||
lists with a 3-arg combiner. Ground-only by composition. 5 new tests.
|
||||
- **2026-05-08** — **take-while-o + drop-while-o**: predicate-driven
|
||||
prefix/suffix split. Roundtrip property verified. 8 new tests.
|
||||
- **2026-05-08** — **arith-progo**: arithmetic-progression list generator
|
||||
via project. 6 new tests.
|
||||
- **2026-05-08** — **counto**: count occurrences of x in l (intarith).
|
||||
6 new tests.
|
||||
- **2026-05-08** — **nub-o**: dedupe via membero-on-tail. Multiplicity
|
||||
caveat documented in tests. 5 new tests.
|
||||
- **2026-05-08** — **simplify-step-o**: algebraic identity simplifier
|
||||
(conda demo). 6 new tests.
|
||||
- **2026-05-08** — **flat-mapo**: concatMap-style relation. 5 new tests.
|
||||
- **2026-05-08** — **foldl-o (relational left fold)**: complement to foldr-o. Combiner has args (acc, head) -> new-acc. (foldl-o pluso-i (1 2 3 4 5) 0 q) -> 15; (foldl-o flipped-conso l () q) reverses l. 5 new tests, 510/510 cumulative.
|
||||
- **2026-05-08** — **foldr-o (relational right fold)**: takes a 3-arg combiner relation rel, a list, an initial accumulator, produces the result. (foldr-o appendo lists () q) is a flatten; (foldr-o conso l () q) rebuilds l. 4 new tests, 505/505 cumulative.
|
||||
- **2026-05-08** — **enumerate-i / enumerate-from-i — 500-test milestone**: index-each-element relations. (enumerate-i l result) -> result is l with each element paired with its 0-based index. (enumerate-from-i n l result) starts at n. 5 new tests, **501/501** cumulative.
|
||||
- **2026-05-08** — **partitiono**: relational partition. (partitiono pred l yes no) splits l so yes contains elements where pred succeeds and no contains the rest. Composes with intarith for numeric predicates. 5 new tests, 496/496 cumulative.
|
||||
- **2026-05-08** — **appendo3**: 3-list append via two appendos. (appendo3 a b c r) is (appendo a b mid) ∧ (appendo mid c r). Recovers any of the three lists given the other two and the result. 5 new tests, 491/491 cumulative.
|
||||
- **2026-05-08** — **lengtho-i**: integer-indexed length (intarith). Empty list -> 0; recurse with pluso-i. Drop-in fast replacement for Peano lengtho when the count fits in a host integer. 5 new tests, 486/486 cumulative.
|
||||
- **2026-05-08** — **sumo + producto (intarith)**: fold a list of ground integers to its sum or product. Empty list -> identity (0 / 1). Recurse via pluso-i / *o-i. 9 new tests, 481/481 cumulative.
|
||||
- **2026-05-08** — **mino + maxo (intarith)**: find the minimum/maximum of a non-empty list of ground integers. Two conde clauses each: singleton (return the element) / multi (compare head against recursive min/max of tail). 9 new tests, 472/472 cumulative.
|
||||
- **2026-05-08** — **sortedo (intarith)**: list is non-decreasing under integer ordering. Three conde clauses: empty / singleton / pair-and-recurse. Uses lteo-i for the adjacent-pair check (ground integers). 7 new tests, 463/463 cumulative.
|
||||
- **2026-05-08** — **subseto**: every element of l1 is a member of l2. Recurses on l1, checking each element via membero. Allows duplicates in l1 (each independently in l2). 7 new tests, 456/456 cumulative.
|
||||
- **2026-05-08** — **cycle-free path search**: mitigation for the cyclic-graph divergence — thread a accumulator through the recursion, gate each step with nafc + membero on visited. Terminates on graphs with cycles; no Phase-7 tabling required for the simple case. 3 new tests, 449/449 cumulative.
|
||||
- **2026-05-08** — **removeo-allo**: removes every occurrence of x (vs rembero, which removes only the first). Three conde clauses: empty -> empty; head matches -> skip and recurse; head differs (nafc) -> keep and recurse. 5 new tests, 446/446 cumulative.
|
||||
- **2026-05-08** — **btree-walko (matche showcase)**: walks a binary tree (:leaf v) | (:node l r) and emits each leaf value via conde. Demonstrates matche dispatch on tagged-list patterns, recursion through both branches via conde, and run* enumerating all 5 leaves of a small tree. 4 new tests, 441/441 cumulative.
|
||||
- **2026-05-08** — **swap-firsto**: swap the first two elements of a list. Built via four conso constraints. Self-inverse on length-2+ lists; runs forward and backward. 6 new tests, 437/437 cumulative.
|
||||
- **2026-05-08** — **pairlisto**: relational zip — pairs is the zipped list of (l1[i] l2[i]). Runs forward, recovers l1 given l2 and pairs, recovers l2 given l1 and pairs. 5 new tests, 431/431 cumulative.
|
||||
- **2026-05-08** — **iterate-no**: relational iterated application. Applies a 2-arg relation n times (Peano n) starting from x to produce result. Generalises succ-iteration / list-cons-iteration / etc. 4 new tests, 426/426 cumulative.
|
||||
- **2026-05-08** — **rev-acco / rev-2o**: accumulator-style reversal — faster than appendo-driven reverseo for forward queries because no quadratic appendos. Trade-off: rev-acco is asymmetric (cannot run cleanly backward in run*). 6 new tests, 422/422 cumulative.
|
||||
- **2026-05-08** — **even-i / odd-i (intarith)**: ground-only parity goals via project. Composes with membero for filtered enumeration: -> . 5 new tests, 416/416 cumulative.
|
||||
- **2026-05-08** — **selecto**: classic miniKanren "choose an element + rest". Direct base (l = (x . rest)) plus skip-head recurse. Enumerates all (element, rest) splits in run*; runs forward, backward, mid-pipeline. 6 new tests, 411/411 cumulative.
|
||||
- **2026-05-08** — **subo (contiguous sublist)**: Two appendos chained — l = front ++ s ++ back. Goal order matters: appendo on the ground l first, so the search is finitary; then constrain front. 7 new tests, 405/405 cumulative.
|
||||
- **2026-05-08** — **prefixo + suffixo**: classic appendo-derived sublist relations. (prefixo p l) ≡ p ⊕ ? = l; (suffixo s l) ≡ ? ⊕ s = l. Both enumerate all prefixes/suffixes when given a fresh first arg. 9 new tests, 398/398 cumulative.
|
||||
- **2026-05-08** — **palindromeo**: 2-line definition (reverseo + ==). Succeeds when a list reads the same forwards and backwards. 7 new tests, 389/389 cumulative.
|
||||
- **2026-05-08** — **not-membero**: relational "x is not a member of l".
|
||||
Uses `nafc + ==` per element (the same skeleton all-distincto uses).
|
||||
Useful as a constraint-style filter: `(membero x dom) (not-membero x
|
||||
excluded)`. 4 new tests, 382/382 cumulative.
|
||||
- **2026-05-08** — **repeato + concato**: repeato builds a list of n copies
|
||||
(Peano n); concato is fold-appendo over a list of lists. Both run forward
|
||||
and backward — repeato can recover the count from a uniform list. 10 new
|
||||
tests, 378/378 cumulative.
|
||||
- **2026-05-08** — **tako + dropo (Peano-indexed prefix/suffix)**: takes / drops
|
||||
the first n elements via a Peano-encoded count. Round-trip
|
||||
`(tako n l) ⊕ (dropo n l) = l` holds. 9 new tests, 368/368 cumulative.
|
||||
- **2026-05-08** — **eveno / oddo Peano predicates**: classic miniKanren parity
|
||||
relations. eveno: 0 or successor-of-successor of even; oddo: 1 or
|
||||
successor-of-successor of odd. Both run forward (test) and backward
|
||||
(enumerate). 12 new tests, 359/359 cumulative.
|
||||
- **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
|
||||
tests, 344/344 cumulative.
|
||||
- **2026-05-08** — **Datalog-style relational database queries**: tests/rdb.sx
|
||||
shows miniKanren as a query engine over fact tables. Defines two tables
|
||||
(employees + project assignments), wraps each with a relation that does
|
||||
membero over the table, then expresses queries: dept filter, salary >
|
||||
threshold (intarith), join engineers ↔ runtime project, find anyone on
|
||||
multiple distinct projects (nafc + ==). 5 new tests, 336/336 cumulative.
|
||||
- **2026-05-08** — **Cyclic graph behaviour test (motivates Phase 7 tabling)**:
|
||||
Demonstrates that naive patho on a 2-cycle generates ever-longer paths.
|
||||
`run 5` truncates to a finite prefix; `run*` would diverge. This is
|
||||
exactly the test case Phase 7 (tabled / SLG resolution) is designed
|
||||
to fix. 3 new tests, 331/331 cumulative.
|
||||
- **2026-05-08** — **numbero / stringo / symbolo (type predicates)**: ground-only
|
||||
type tests via project. Compose with `membero` for type-filtered enumeration:
|
||||
`(fresh (x) (membero x (1 "a" 2 "b" 3)) (numbero x) (== q x))` → `(1 2 3)`.
|
||||
Note: SX keywords are strings, so `(stringo :k)` succeeds. 12 new tests,
|
||||
328/328 cumulative.
|
||||
- **2026-05-08** — **Graph reachability via patho**: classic miniKanren
|
||||
graph search. `edgeo` looks up edges in a fact list via `membero`; `patho`
|
||||
recursively builds paths via direct-edge OR (one edge + recurse + cons).
|
||||
Enumerates all paths between two nodes, including alternates through
|
||||
shortcuts. 6 new tests, 316/316 cumulative.
|
||||
- **2026-05-08** — **everyo / someo (predicate-style relations)**:
|
||||
`(everyo rel l)` — every element of l satisfies rel; `(someo rel l)` —
|
||||
some element does. Both compose with intarith for numeric tests:
|
||||
`(everyo (fn (x) (lto-i x 10)) (list 1 5 9))` succeeds. 10 new tests,
|
||||
310/310 cumulative.
|
||||
- **2026-05-08** — **mapo (relational map) — 300 test milestone**: `(mapo
|
||||
rel l1 l2)` succeeds when l2 is l1 with each element rel-related. Works
|
||||
forward and backward; composes with intarith — `(mapo (fn (a b) (*o-i
|
||||
a a b)) (1 2 3 4) q)` → `((1 4 9 16))`. 7 new tests, **300/300** cumulative.
|
||||
- **2026-05-08** — **samelengtho**: classic miniKanren relation that
|
||||
succeeds when two lists have equal length. Symmetric — works to enumerate
|
||||
both lists fresh: `(run 3 q (fresh (l1 l2) (samelengtho l1 l2) (== q
|
||||
(list l1 l2))))` produces empty/empty, then 1-elem pairs, then 2-elem.
|
||||
5 new tests, 293/293 cumulative.
|
||||
- **2026-05-08** — **Pythagorean triples (intarith showcase)**: search for
|
||||
(a, b, c) ∈ [1..10]³, a ≤ b, a² + b² = c² via `ino + lteo-i + *o-i +
|
||||
pluso-i + ==`. Finds exactly `(3 4 5)` and `(6 8 10)`. Demonstrates the
|
||||
enumerate-then-filter pattern with intarith escape into host arithmetic.
|
||||
1 new test, 288/288 cumulative.
|
||||
- **2026-05-08** — **intarith.sx — fast ground-only integer arithmetic**:
|
||||
pluso-i / minuso-i / *o-i / lto-i / lteo-i / neqo-i wrap host arithmetic
|
||||
via `project`. They are not relational like Peano `pluso` (require args
|
||||
to walk to numbers), but run at native host speed — a viable escape
|
||||
hatch when puzzle size makes Peano impractical. Composes with relational
|
||||
goals: `(membero x dom) (lto-i x 3)` filters dom by `< 3`. 18 new tests,
|
||||
287/287 cumulative.
|
||||
- **2026-05-08** — **2x2 Latin square**: small classic constraint demo using
|
||||
`ino` + 4 `all-distincto` constraints. Enumerates exactly 2 squares
|
||||
(`((1 2)(2 1))` and `((2 1)(1 2))`); a clue (top-left = 1) narrows to one.
|
||||
3 new tests, 269/269 cumulative. Note: 3x3 (12 squares) is the natural
|
||||
showcase but too slow under naive enumerate-then-filter — needs Phase 6
|
||||
arc-consistency.
|
||||
- **2026-05-08** — **rembero / assoco / nth-o**: more standard list relations.
|
||||
rembero removes the first occurrence (uses `nafc (== a x)` to gate the
|
||||
skip clause, so it's well-defined on ground lists). assoco is alist
|
||||
lookup — works forward (key → value) and backward (value → key). nth-o
|
||||
uses Peano indices into a list. 13 new tests, 266/266 cumulative.
|
||||
- **2026-05-08** — **flatteno**: nested-list flattener via conde + appendo.
|
||||
Atom-ness is detected via `nafc (nullo ...) + nafc (pairo ...)` so the
|
||||
third clause only fires when the input is a ground non-list. Works for
|
||||
`()` / atoms / flat lists / arbitrarily-nested lists. 7 new tests,
|
||||
253/253 cumulative. Phase 4 list relations all complete.
|
||||
- **2026-05-08** — **Laziness tests**: explicitly verifies the
|
||||
Zzz-on-conde-clauses + mk-mplus-swap-on-paused machinery: an
|
||||
infinitely-recursive relation truncated via `run 4 q (listo-aux q)`
|
||||
produces exactly `(() (_.0) (_.0 _.1) (_.0 _.1 _.2))`; mixing two
|
||||
infinite generators via `mk-disj` keeps both alive (no starvation);
|
||||
`run*` terminates on a bounded query. 3 new tests, 246/246 cumulative.
|
||||
- **2026-05-08** — **N-queens (classic miniKanren benchmark green)**:
|
||||
`lib/minikanren/queens.sx`. Encoding cols(i) = column of queen in row i;
|
||||
`ino-each` + `all-distincto` cover row/column constraints; `safe-diag`
|
||||
uses `project` to escape into host arithmetic for the `|c_i - c_j| ≠
|
||||
|i - j|` diagonal check; `all-cells-safe` walks pairs at construction
|
||||
time. `(run* q (fresh (a b c d) (== q (list a b c d)) (queens-cols
|
||||
(list a b c d) 4)))` returns the two valid 4-queens placements
|
||||
`(2 4 1 3)` and `(3 1 4 2)`. 6 new tests, 243/243 cumulative.
|
||||
- **2026-05-08** — **Phase 6 piece A — minimal FD (ino + all-distincto)**:
|
||||
`lib/minikanren/fd.sx`. `ino` is `membero` with the FD-style argument
|
||||
order; `all-distincto` is `nafc + membero` walking the list. Together
|
||||
they cover the enumerate-then-filter style of finite-domain solving —
|
||||
`(fresh (a b c) (ino a D) (ino b D) (ino c D) (all-distincto (list a b c)))`
|
||||
enumerates all distinct triples from D. Full FD with arc-consistency,
|
||||
fd-plus etc. is still pending. 9 new tests, 237/237 cumulative.
|
||||
- **2026-05-08** — **Classic puzzles + matche keyword fix**: matche now emits
|
||||
keywords bare in the pattern->expr conversion so they self-evaluate to their
|
||||
string name and unify with the same-keyword target value (instead of becoming
|
||||
a quoted-keyword type). New `tests/classics.sx`: pet permutation puzzle,
|
||||
parent/grandparent inference over a fact list, symbolic differentiation
|
||||
driven by matche dispatch on `:x` / `(:+ a b)` / `(:* a b)` patterns.
|
||||
6 new tests, 228/228 cumulative.
|
||||
- **2026-05-08** — **Phase 5 piece D — matche, Phase 5 done**: pattern matching
|
||||
macro (`lib/minikanren/matche.sx`) — symbols become fresh vars, atoms become
|
||||
literals, lists recurse positionally, repeated names unify. 14 new tests
|
||||
(literals, vars, wildcards, list patterns, multi-clause dispatch, nested
|
||||
patterns, repeated-var-implies-eq). Built via `cons`/`list` rather than
|
||||
quasiquote because SX's quasiquote does not recurse into lambda bodies — a
|
||||
worth-knowing gotcha. 222/222 cumulative.
|
||||
- **2026-05-08** — **Phase 4 piece C — permuteo + inserto**: standard recursive
|
||||
insert-at-any-position + permute-tail. 7 new tests, including all-6-perms-of-3
|
||||
as a set check. 208/208 cumulative.
|
||||
- **2026-05-07** — **Phase 5 piece C — nafc**: `lib/minikanren/nafc.sx`. Three-line
|
||||
primitive: stream-take 1; if empty, `(unit s)`, else `mzero`. 7 tests including
|
||||
double-negation and use as a guard. 201/201 cumulative.
|
||||
- **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)
|
||||
(== 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.
|
||||
169/169 cumulative.
|
||||
- **2026-05-07** — **Phase 4 piece B — reverseo + lengtho**: reverseo runs forward
|
||||
cleanly and `run 1`-cleanly backward; lengtho uses Peano-encoded lengths so it
|
||||
works as a true relation in both directions (tests use the encoding directly).
|
||||
10 new tests, 162/162 cumulative.
|
||||
- **2026-05-07** — **Phase 4 piece A — appendo canary green**: cons-cell support
|
||||
in `unify.sx` + `(:s head tail)` lazy stream refactor in `stream.sx` + hygienic
|
||||
`Zzz` (gensym'd subst-name) wrapping each `conde` clause + `lib/minikanren/
|
||||
relations.sx` with `nullo` / `pairo` / `caro` / `cdro` / `conso` / `firsto` /
|
||||
`resto` / `listo` / `appendo` / `membero`. 25 new tests in `tests/relations.sx`,
|
||||
152/152 cumulative.
|
||||
- **Three deep fixes shipped together**, all required to make `appendo`
|
||||
terminate in both directions:
|
||||
1. SX has no improper pairs, so a stream cell of mature subst + thunk
|
||||
tail can't use `cons` — moved to a `(:s head tail)` tagged shape.
|
||||
2. `(Zzz g)` wrapped its inner fn in a parameter named `s`, capturing
|
||||
the user goal's own `s` binding (the `(appendo l s ls)` convention).
|
||||
Replaced with `(gensym "zzz-s-")` for hygiene.
|
||||
3. SX cons cells `(:cons h t)` for relational decomposition (so
|
||||
`(conso a d l)` can split a list by head/tail without proper
|
||||
improper pairs); `mk-walk*` re-flattens cons cells back to native
|
||||
lists for clean reification output.
|
||||
- **2026-05-07** — **Phase 3 done** (run + reification): `lib/minikanren/run.sx` (~28 lines).
|
||||
`reify`/`reify-s`/`reify-name` for canonical `_.N` rendering of unbound vars in
|
||||
left-to-right occurrence order; `run*` / `run` / `run-n` defmacros. 18 new tests
|
||||
in `tests/run.sx`, including the **first classic miniKanren tests green**:
|
||||
`(run* q (== q 1))` → `(1)`; `(run* q (fresh (x y) (== q (list x y))))` →
|
||||
`((_.0 _.1))`. 128/128 cumulative.
|
||||
- **2026-05-07** — **Phase 2 piece D + done** (`condu` / `onceo`): `lib/minikanren/condu.sx`.
|
||||
Both are commitment forms: `onceo` is `(stream-take 1 ...)`; `condu` walks clauses
|
||||
and commits the first one whose head produces an answer. 10 tests in `tests/condu.sx`,
|
||||
110/110 cumulative. Phase 2 complete — ready for Phase 3 (run + reification).
|
||||
- **2026-05-07** — **Phase 2 piece C** (`conde`): `lib/minikanren/conde.sx` — single
|
||||
defmacro folding clauses through `mk-disj` with internal `mk-conj`. 9 tests in
|
||||
`tests/conde.sx`, 100/100 cumulative. Confirmed eager DFS ordering for ==-only
|
||||
streams; true interleaving is a Phase 4 concern (paused thunks under recursion).
|
||||
- **2026-05-07** — **Phase 2 piece B** (`fresh`): `lib/minikanren/fresh.sx` (~10 lines).
|
||||
defmacro form for nice user-facing syntax + `call-fresh` for programmatic use.
|
||||
9 new tests in `tests/fresh.sx`, 91/91 cumulative.
|
||||
- **2026-05-07** — **Phase 2 piece A** (streams + ==/conj/disj): `lib/minikanren/stream.sx`
|
||||
(mzero/unit/mk-mplus/mk-bind/stream-take, ~25 lines of code) + `lib/minikanren/goals.sx`
|
||||
(succeed/fail/==/==-check/conj2/disj2/mk-conj/mk-disj, ~30 lines). Found and noted
|
||||
a host-primitive name clash: `bind` is built in and silently shadows user defines —
|
||||
must use `mk-bind`/`mk-mplus` etc. throughout. 34 tests in `tests/goals.sx`,
|
||||
82/82 cumulative all green. fresh/conde/condu/onceo still pending.
|
||||
- **2026-05-07** — **Phase 1 done**: `lib/minikanren/unify.sx` (53 lines, ~22 lines of actual code) +
|
||||
`lib/minikanren/tests/unify.sx` (48 tests, all green). Kit consumption: `walk-with`,
|
||||
`unify-with`, `occurs-with`, `extend`, `empty-subst`, `mk-var`, `is-var?`, `var-name`
|
||||
all supplied by `lib/guest/match.sx`. Local additions: a miniKanren-flavoured cfg
|
||||
(treats native SX lists as cons-pairs via `:ctor-head = :pair`, occurs-check off),
|
||||
`make-var` fresh-counter, deep `mk-walk*` (kit's `walk*` only recurses into `:ctor`
|
||||
form, not native lists), and `mk-unify` / `mk-unify-check` thin wrappers. The kit
|
||||
earns its keep ~3× over by line count — confirms lib-guest match kit is reusable
|
||||
for logic-language hosts as designed.
|
||||
|
||||
Reference in New Issue
Block a user