Commit Graph

674 Commits

Author SHA1 Message Date
1d7400a54a mk: phase 7 piece A — SLG-style tabling with in-progress sentinel
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Solves the canonical cyclic-graph divergence problem from the deferred
plan. Naive memoization (table-1/2/3 in tabling.sx) drains the body's
answer stream eagerly; cyclic recursive calls with the same ground key
diverge before populating the cache.

table-2-slg / table-3-slg add an in-progress sentinel: before
evaluating the body, mark the cache entry :in-progress. Any recursive
call to the same key sees the sentinel and returns mzero (no answers
yet). Outer recursion thus terminates on cycles. After the body
finishes, the sentinel is replaced with the actual answer-value list.

Demo: tab-patho with a 3-edge graph (a -> b, b -> a, b -> c).
  (run* q (tab-patho :a :c q))   -> ((:a :b :c))   ; finite
  (run* q (tab-patho :a :a q))   -> ((:a :b :a))   ; one cycle visit
  (run* q (tab-patho :a :b q))   -> ((:a :b))      ; direct edge

Without SLG, all three diverge.

Limitation: single-pass — answers found by cycle-dependent recursive
calls are not iteratively re-discovered. Full SLG with fixed-point
iteration (re-running until no new answers) is left for follow-up.

5 new tests including SLG-fib for sanity (matches naive table-2),
3 cyclic patho cases.
2026-05-09 14:10:43 +00:00
0cb0c1b782 mk: phase 5 polish — =/= disequality with constraint store
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
(=/= u v) posts a closure to the same _fd constraint store the
CLP(FD) goals use; the closure is fd-fire-store-driven, so it
re-checks after every binding.

Semantics:
  - mk-unify u v s; nil result -> distinct, drop the constraint.
  - unify succeeded with no new bindings (key-count unchanged) -> equal,
    fail.
  - otherwise -> partially unifiable, keep the constraint.

==-cs is the constraint-aware drop-in for == that fires fd-fire-store
after the binding; plain == doesn't reactivate the store, so a binding
that should violate a pending =/= would go undetected. Use ==-cs
whenever a program mixes =/= (or fd-* goals re-checked after non-FD
bindings) with regular unification.

12 new tests covering ground/structural/late-binding cases; 60/60
clpfd-and-diseq tests pass.
2026-05-09 14:08:44 +00:00
2921aa30b4 mk: phase 6 piece D — send-more-money + Sudoku 4x4
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Two CLP(FD) demo puzzles plus an underlying improvement.

clpfd.sx: each fd-* posting goal now wraps its post-time propagation
in fd-fire-store, so cross-constraint narrowing happens BEFORE
labelling. Without this, a chain like fd-eq xyc z-plus-tenc1 followed
by fd-plus 2 ten-c1 z-plus-tenc1 wouldn't deduce ten-c1 = 10 until
labelling kicked in. Now the deduction happens at goal-construction
time. Guard against (c s2) returning nil before fd-fire-store runs.

tests/send-more-money.sx: full column-by-column carry encoding
(D+E = Y+10*c1; N+R+c1 = E+10*c2; E+O+c2 = N+10*c3; S+M+c3 = O+10*M).
Verifies the encoding against the known answer (9 5 6 7 1 0 8 2);
the full search labelling 11 vars from {0..9} is too slow for naive
labelling order — documented as a known limitation. Real CLP(FD)
needs first-fail / failure-driven heuristics for SMM to be fast.

tests/sudoku-4x4.sx: 16 cells / 12 distinctness constraints. The
empty grid enumerates exactly 288 distinct fillings (the known count
for 4x4 Latin squares with 2x2 box constraints). An impossible-clue
test (two 1s in row 0) fails immediately.

50/50 sudoku + smm tests, full clpfd suite green at 132/132.
2026-05-09 14:06:47 +00:00
d1817e026d mk: phase 6 piece B — bounds-consistency for fd-plus + fd-times
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
fd-plus-prop now propagates in the four partial- and all-domain cases
(vvn, nvv, vnv, vvv) by interval reasoning:
  x in [z.min - y.max .. z.max - y.min]
  y in [z.min - x.max .. z.max - x.min]
  z in [x.min + y.min .. x.max + y.max]

Helpers added:
  fd-narrow-or-skip — common "no-domain? skip; else filter & set" path.
  fd-int-floor-div / fd-int-ceil-div — integer-division wrappers because
    SX `/` returns rationals; floor/ceil computed via (a - (mod a b)).

fd-times-prop gets the same treatment for positive domains. Mixed-sign
domains pass through (sound, but no narrowing).

10 new tests in clpfd-bounds.sx demonstrate domains shrinking BEFORE
labelling: x+y=10 with x in {1..10}, y in {1..3} narrows x to {7..9};
3*y=z narrows z to {3..12}; impossible bounds (x+y=100, x,y in {1..10})
return :no-subst directly. 132/132 across the clpfd test files.

Suggested next: Piece D (send-more-money + Sudoku 4x4) to validate
this against larger puzzles.
2026-05-09 13:18:29 +00:00
80ab039ada mk: phase 7 — table-1 + table-3, Ackermann canary
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Two more arities of the naive memoization wrapper:

table-1: predicate (1-arg) tabling. Cache entry is :ok / :no.
  Demonstrated with a tabled membero-as-predicate.

table-3: 3-arg (i1 i2 output) tabling. Cache key joins the two
  inputs; cache value is the output value list.
  Canonical demo: tabled Ackermann.

  (ack-o 0 0 q) -> 1
  (ack-o 2 3 q) -> 9
  (ack-o 3 3 q) -> 61

A(3,3) executes A(2,..) many times, A(1,..) more, A(0,..) most. With
table-3 each (m, n) pair is computed once.

6 new tests, 644/644 cumulative.
2026-05-08 22:29:15 +00:00
adc8467c78 mk: phase 7 — naive ground-arg tabling, Fibonacci canary green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 58s
`table-2` wraps a 2-arg (input, output) relation. On a ground input
walk, looks up the (string-encoded) cache key; on miss, runs the
relation, drains the answer stream, extracts walk*-output values from
each subst, stores them, and replays. On hit, replays the cached
values directly — no recomputation.

Cache lifetime: a single global mk-tab-cache (mutated via set!).
mk-tab-clear! resets between independent queries.

Canonical demo: tabled fib(25) = 75025 in ~5 seconds; the same naive
fib-o times out at 60s. Memoization collapses the exponential redundant
recomputation in the binary recursion.

Limitations (deferred to future SLG work): cyclic recursive calls with
the same ground key still diverge — naive memoization populates the
cache only AFTER computation completes, so a recursive call inside its
own computation can't see the in-progress entry. The brief's "tabled
patho on cyclic graphs" use case requires producer/consumer
scheduling and is left for a future iteration.

12 new tests, fib(0..20) + ground-term predicate + cache-replay
verification. 638/638 cumulative.
2026-05-08 22:27:10 +00:00
8644668fc9 mk: phase 6 done — fd-fire-store iterates, N-queens FD works
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
The previous fd-fire-store fired every constraint exactly once. That
left the propagation incomplete in chains like
  fd-plus c4 1 a; fd-neq c3 a
where, on the round c4 binds, fd-plus binds a, but fd-neq c3 a was
already past — so the conflict went undetected.

New: fd-store-signature is sum-of-domain-sizes + count-of-bindings.
fd-fire-store calls fd-fire-list and recurses while the signature
strictly decreases. Reaches a fixed point or fails.

This makes N-queens via FD tractable:
  4-queens -> ((2 4 1 3) (3 1 4 2)) — exactly the two solutions.
  5-queens -> 10 solutions (the canonical count), in seconds.

Phase 6 marked complete in the plan: domains, fd-in, fd-eq, fd-neq,
fd-lt, fd-lte, fd-plus, fd-times, fd-distinct, fd-label, all wired
through the constraint-reactivation loop.

Two new tests, 626/626 cumulative.
2026-05-08 14:44:59 +00:00
a6e758664b mk: phase 6H — fd-times (x * y = z)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Ground-cases propagator parallel to fd-plus. Division back-direction
checks (mod z x) = 0 before recovering a divisor. Edge cases:
multiplying by zero binds the product to zero; with z=0 and one
factor zero, the other factor is unconstrained.

7 tests including divisor enumeration, square-of-each, divisibility
rejection. 624/624 cumulative.
2026-05-08 14:37:17 +00:00
5d3c248fdd mk: phase 6G — fd-plus (x + y = z)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Ground-cases propagator: when at least two of {x, y, z} walk to
ground numbers, the third is derived (or checked, if also ground).
Three vars with domains: deferred — no bounds-consistency in this
iteration.

Includes a small fd-bind-or-narrow helper that handles the common
"bind a var to a target int, respecting any existing domain"
pattern shared across propagators.

7 new tests: ground/ground/ground, recover x, recover y, impossible
case, domain-check rejection, x+y=5 enumeration, large numbers.
617/617 cumulative.
2026-05-08 14:36:25 +00:00
f88388b2f9 mk: phase 6F — fd-distinct (pairwise alldifferent)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
(fd-distinct (list a b c ...)) imposes pairwise distinctness via O(n²)
fd-neq constraints. Each fd-neq propagates independently when any pair
becomes ground or has a domain-removable value.

Tests: empty/singleton trivially succeed; pair-distinct/equal cover
correctness; 3-perms-of-3 = 6 and 4-perms-of-4 = 24 confirm full
permutation enumeration; pigeonhole 4-of-3 fails.

7 new tests, 610/610 cumulative.
2026-05-08 14:35:19 +00:00
c01ddc2b23 mk: phase 6E — fd-lt + fd-lte + fd-eq with propagation
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s
Three more constraint goals built on the same propagator-store
machinery as fd-neq:

fd-lt: x < y. Ground/ground compares; var/num filters domain;
var/var narrows x's domain to (< y-max) and y's to (> x-min).

fd-lte: ≤ variant.

fd-eq: x = y. Ground/ground checks. Var/num: requires num to be in
var's domain (or var unconstrained) before binding. Var/var: intersect
domains, narrow both, then unify the vars.

10 new tests: narrowing against ground, ordered-pair generation,
chained x<y<z determinism, domain-sharing, out-of-domain rejection.
603/603 cumulative (100/100 across the four CLP(FD) test files).
2026-05-08 14:34:10 +00:00
27637aa0f9 mk: phase 6D — fd-neq with propagation + constraint reactivation
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
fd-neq adds a closure to the constraint store and runs it once on
post. After every label binding, fd-fire-store re-runs all stored
constraints — when one side of a fd-neq later becomes ground, the
domain of the other side has the value removed.

Propagator semantics:
  (number, number)  -> equal? fail : ok
  (number, var)     -> remove number from var's domain
  (var, number)     -> symmetric
  (var, var)        -> defer (re-fires after each label step)

Pigeonhole-fails test confirms the constraint flow ends correctly:
3 vars all-pairwise-distinct over a 2-element domain has no solutions.

7 new tests, 593/593 cumulative.
2026-05-08 14:24:28 +00:00
f2817bb6be mk: phase 6C — fd-in + fd-label (domain narrowing + labelling)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
fd-in x dom-list: narrows x's domain. If x is a ground number, checks
membership; if x is a logic var, intersects existing domain (or sets
fresh) and stores via fd-set-domain. Fails if domain becomes empty.

fd-label vars: drives search by enumerating each var's domain. Each
var is unified with each value in its domain, in order, via mk-mplus
of singleton streams.

Forward: (fd-in x dom) (fd-label (list x)) iterates x over dom.
Intersection: two fd-in goals on the same var compose via dom-intersect.
Disjoint domains -> empty answer set. Ground value membership check
gates pass/fail. Composes with the rest of the miniKanren machinery —
fresh / conde / membero etc. all work alongside.

9 new tests, 586/586 cumulative.
2026-05-08 14:09:18 +00:00
c71da0e1cf mk: phase 6B — clpfd.sx domain primitives
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 58s
Foundation for native CLP(FD). The substitution dict carries a reserved
"_fd" key holding a constraint store:
  {:domains {var-name -> sorted-int-list}
   :constraints (... pending constraints ...)}

This commit ships only the domain machinery + accessors:
  fd-dom-from-list / fd-dom-range / fd-dom-empty? / fd-dom-singleton?
  fd-dom-min / fd-dom-max / fd-dom-member? / fd-dom-intersect /
  fd-dom-without

  fd-store-of / fd-domain-of / fd-set-domain / fd-with-store

fd-set-domain returns nil when the domain becomes empty (failure),
which is the wire signal subsequent constraint goals will consume.
The constraints field is reserved for the next iteration.

26 new tests, 577/577 cumulative.
2026-05-08 14:06:19 +00:00
f8b9bde1a5 mk: zip-with-o — element-wise combine of two lists
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Like Haskell's zipWith but relational. (zip-with-o rel l1 l2 result)
applies a 3-arg combiner relation pointwise: rel l1[i] l2[i] result[i].

  (zip-with-o pluso-i (list 1 2 3) (list 10 20 30) q)
    -> ((11 22 33))

  (zip-with-o (fn (a b r) (== r (list a b))) (list :x :y) (list 1 2) q)
    -> (((:x 1) (:y 2)))

Different-length lists fail.

5 new tests, 551/551 cumulative.
2026-05-08 12:31:53 +00:00
2a36e692f4 mk: take-while-o + drop-while-o — predicate-driven prefix/suffix
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
(take-while-o pred l result): take elements from l while pred holds,
stopping at the first element that fails. (drop-while-o pred l result):
drop matching elements, return the rest including the first non-match.

Together: (take-while p l) ⊕ (drop-while p l) = l, verified by an
end-to-end roundtrip test.

8 new tests, 546/546 cumulative.
2026-05-08 12:30:53 +00:00
d1e00e2e9e mk: arith-progo — arithmetic progression generation
Some checks are pending
Test, Build, and Deploy / test-build-deploy (push) Has started running
(arith-progo start step len result): result is the list
(start, start+step, ..., start+(len-1)*step). Length 0 yields the
empty list. Negative steps and zero step are supported.

Useful for FD-style domain construction:
  (arith-progo 1 1 9 dom)  -> (1 2 3 4 5 6 7 8 9)

6 new tests, 538/538 cumulative.
2026-05-08 12:29:34 +00:00
de6fd1b183 mk: counto — count occurrences of x in l (intarith)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Walks the list with a recursive count. On a head match, recurse and
add 1 via pluso-i; on no match (nafc), recurse forwarding the count.
Empty list yields 0.

6 new tests, 532/532 cumulative.
2026-05-08 12:28:31 +00:00
f4a902a6df mk: nub-o — dedupe by keeping the last occurrence
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Walks the list; if the head appears in the tail (membero), drop it and
recurse; otherwise keep it and recurse. Result preserves only the
*last* occurrence of each value.

Caveat: with input like (1 1 1) the membero check succeeds with
multiplicity, so multiple (1) answers may emerge — each is shape-
identical, but the test deliberately checks every-result-is-(1) rather
than asserting answer count.

5 new tests, 526/526 cumulative.
2026-05-08 12:27:03 +00:00
d891831f08 mk: simplify-step-o — algebraic-identity simplifier (conda demo)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Demonstrates conda for first-match-wins dispatch over a set of rewrite
rules: 0+x = x, x+0 = x, 0*y = 0, x*0 = 0, 1*x = x, x*1 = x, default
unchanged.

Six rules + a fall-through default, all wrapped in a single conda. The
first clause whose head succeeds commits to that rewrite. The fall-
through default ensures the relation always succeeds with at least the
unchanged input.

6 new tests, 521/521 cumulative.
2026-05-08 12:25:36 +00:00
091030f13e mk: flat-mapo — concatMap-style relation
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
(flat-mapo rel l result): each element x of l is mapped to a list via
rel x list-from-x, and all such lists are concatenated to form result.

  (flat-mapo (fn (x r) (== r (list x x))) (list 1 2 3) q)
    -> ((1 1 2 2 3 3))

5 new tests, 515/515 cumulative.
2026-05-08 12:24:23 +00:00
f5ab66e1a3 mk: foldl-o — relational left fold
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
Complement to foldr-o. The combiner relation has signature
(acc head new-acc) — accumulator first.

Examples:
  (foldl-o pluso-i (list 1 2 3 4 5) 0 q)  -> (15)
  (foldl-o *o-i    (list 1 2 3 4)   1 q)  -> (24)
  (foldl-o (fn (acc x r) (conso x acc r))  ; flipped conso
           (list 1 2 3 4) (list) q)        -> ((4 3 2 1))   ; reverse

5 new tests, 510/510 cumulative.
2026-05-08 12:23:40 +00:00
3842496f3b mk: foldr-o — relational right fold
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Takes a 3-arg combiner relation, a list, and an initial accumulator,
produces the right-fold result. (rel a tail-result result) combines
the head with the recursive result.

Examples:
  (foldr-o appendo (list (list 1 2) (list 3) (list 4 5)) (list) q)
    -> ((1 2 3 4 5))                ; flatten

  (foldr-o conso (list 1 2 3) (list) q)
    -> ((1 2 3))                    ; rebuild list

4 new tests, 505/505 cumulative.
2026-05-08 12:21:42 +00:00
08f4a7babd mk: enumerate-i / enumerate-from-i — 501/501 milestone
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
(enumerate-i l result): result is l with each element paired with its
0-based index. (enumerate-from-i n l result): same but starts at n.

  (enumerate-i (list :a :b :c) q) -> (((0 :a) (1 :b) (2 :c)))

5 new tests, 501/501 cumulative.
2026-05-08 12:20:03 +00:00
221c7fef35 mk: partitiono — split list by predicate
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
(partitiono pred l yes no) — yes is the elements of l where pred
succeeds; no is the rest. Conde dispatches on each element via the
predicate goal vs nafc-of-the-predicate, threading the head through
the matching output list.

Composes with intarith / membero / etc. for any predicate-shaped goal:
  (partitiono (fn (x) (lto-i x 5)) (list 1 7 2 8 3) yes no)
    yes -> (1 2 3); no -> (7 8)

5 new tests, 496/496 cumulative.
2026-05-08 12:18:25 +00:00
363ebc8f04 mk: appendo3 — 3-list append
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Composes two appendos: (appendo a b mid) ∧ (appendo mid c r). Runs
forward (concatenate three known lists) and backward (recover any of
the three from the other two and the result).

5 new tests, 491/491 cumulative.
2026-05-08 12:16:40 +00:00
7ff72cefb2 mk: lengtho-i — integer-indexed length
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Drop-in fast replacement for Peano lengtho when the count fits in a
host integer. Two conde clauses: empty list -> 0; recurse, n = 1 +
length(tail). Uses pluso-i so the length walks to a native int.

5 new tests, 486/486 cumulative.
2026-05-08 12:15:53 +00:00
064ab2900b mk: sumo + producto — fold list to integer
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Sum and product over a list of ground integers via fold + intarith.
Empty list yields the identity (0 for sum, 1 for product). Recurse
combines the head with the recursively-computed tail value via
pluso-i / *o-i.

9 new tests, 481/481 cumulative.
2026-05-08 12:14:15 +00:00
4f5f8015fb mk: mino + maxo — find min/max of a list
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Two conde clauses each: singleton -> the element; multi -> compare head
against the recursive min/max of the tail and pick. Uses lteo-i / lto-i
for the comparisons, so the input must be ground integers.

mino + maxo can run together: (fresh (mn mx) (mino l mn) (maxo l mx)
(== q (list mn mx))) recovers both.

9 new tests, 472/472 cumulative.
2026-05-08 12:12:32 +00:00
c4b6f1fa0f mk: sortedo — list is non-decreasing (intarith)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Three conde clauses: empty list / singleton list / two-or-more (where
the first two satisfy lteo-i and the rest is recursively sorted). Uses
ground-only integer comparison (intarith), so the input list must
walk to ground integers.

7 new tests, 463/463 cumulative.
2026-05-08 12:10:52 +00:00
6454603568 mk: subseto — every element of l1 is in l2
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Recursive: empty l1 trivially holds; otherwise the head is in l2 (via
membero) and the tail is a subset. Duplicates in l1 are allowed since
each is independently checked.

7 new tests, 456/456 cumulative.
2026-05-08 12:09:06 +00:00
4df277803d mk: cycle-free path search test
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Mitigation for the cyclic-graph divergence (see tests/cyclic-graph.sx).
Threads a `visited` accumulator through the recursion; each candidate
next-step is gated by `nafc (membero z visited)`. Terminates on graphs
with cycles, no Phase-7 tabling required for the simple acyclic-path
query.

Demonstrates a viable alternative to tabling for the common case where
the user wants finite path enumeration over a graph with cycles.

3 new tests, 449/449 cumulative.
2026-05-08 12:07:00 +00:00
58d78de32a mk: removeo-allo — remove every occurrence
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Three conde clauses: empty list -> empty result; head matches x ->
skip and recurse; head differs (nafc-gated) -> keep and recurse.
Distinct from rembero, which removes only the first occurrence.

5 new tests, 446/446 cumulative.
2026-05-08 12:04:17 +00:00
6bc3c14dac mk: btree-walko — binary-tree walker via matche
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Demo of matche dispatch + conde + recursion for tree traversal:
  (matche tree
    ((:leaf x)   (== v x))
    ((:node l r) (conde ((btree-walko l v)) ((btree-walko r v)))))

Test tree ((1 2) (3 (4 5))) yields all 5 leaves under run*. Also tests
membership (run 1) and absence.

4 new tests, 441/441 cumulative.
2026-05-08 12:02:13 +00:00
eb69039935 mk: swap-firsto — swap first two list elements
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 48s
Four conso calls express the (a b . rest) -> (b a . rest) rewrite as a
purely relational constraint. Self-inverse on length-2+ lists; runs
forward (swap given input) and backward (recover original from the
swapped form). Fails on lists shorter than 2.

6 new tests, 437/437 cumulative.
2026-05-08 11:59:41 +00:00
c04ddd105b mk: pairlisto — relational zip
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
(pairlisto l1 l2 pairs): pairs is the zipped list of pairs (l1[i] l2[i]).
Recurses on both l1 and l2 in lockstep, building pairs in parallel.

Runs forward, can recover l1 given l2 and pairs, can recover l2 given
l1 and pairs. Different-length lists fail.

5 new tests, 431/431 cumulative.
2026-05-08 11:57:12 +00:00
136cacbd3f mk: iterate-no — apply a relation n times
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
(iterate-no rel n x result) holds when applying the 2-arg relation rel
n times (Peano n) starting from x produces result. Base case: zero
iterations means result equals x. Recursive case: rel x mid, then
iterate-no n-1 from mid.

Generalises common chains:
  succ iteration:  (iterate-no succ-rel n :z q) -> n in Peano
  list growth:     (iterate-no cons-rel n () q) -> n-element list

4 new tests, 426/426 cumulative.
2026-05-08 11:54:24 +00:00
6fc155ddd8 mk: rev-acco + rev-2o — accumulator-style reverse
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
rev-acco is the standard tail-recursive reverse with an accumulator;
rev-2o starts the accumulator at the empty list. Faster than the
appendo-driven reverseo for forward queries because there is no nested
appendo per element.

Trade-off: rev-acco is asymmetric. The accumulator's initial-empty
cannot be enumerated backwards the way reverseo does, so reverseo is
still the right choice when both directions matter.

A test verifies rev-2o and reverseo agree on forward queries.

6 new tests, 422/422 cumulative.
2026-05-08 11:51:51 +00:00
d992788a03 mk: even-i / odd-i — host-arithmetic parity goals
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Two-line definitions over project + host even?/odd?. Ground-only — no
relational behaviour, but they pair cleanly with membero for filtered
enumeration:
  (fresh (x) (membero x (list 1 2 3 4 5 6)) (even-i x) (== q x))
    -> (2 4 6)

5 new tests, 416/416 cumulative.
2026-05-08 11:49:47 +00:00
4d861575df mk: selecto — choose element + rest of list
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Classic miniKanren relation. (selecto x rest l) holds when l contains
x at any position with `rest` being everything else. Direct base case
(l = (x . rest)) plus the skip-head recursion that threads the head
through to the result rest.

Run modes: enumerate every (x, rest) split; recover rest given an
element; recover an element given the rest; (and ground/all combinations).

6 new tests, 411/411 cumulative.
2026-05-08 11:47:27 +00:00
e202c81a0d mk: subo — contiguous sublist relation
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
Composes two appendos: l = front ++ s ++ back, equivalently
  (appendo front-and-s back l) and (appendo front s front-and-s).

Goal order matters: doing the (appendo ground:l) split first makes the
search finitary; the second appendo is then deterministic given
front-and-s and ground s. Reversing the order causes divergence on
failing inputs (the front search becomes unbounded).

7 new tests, 405/405 cumulative.
2026-05-08 11:45:31 +00:00
fc14a8063b mk: prefixo + suffixo — appendo-derived sublist relations
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Two-line definitions over appendo:
  (prefixo p l) ≡ ∃rest. (appendo p rest l)
  (suffixo s l) ≡ ∃front. (appendo front s l)

Both enumerate all prefixes/suffixes when called with a fresh first
arg, and serve as decision relations when called with both grounded.

9 new tests, 398/398 cumulative.
2026-05-08 11:40:28 +00:00
6ee02db2ab mk: palindromeo — list reads same forwards/backwards
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Two-line definition: a list is a palindrome iff it equals its reverse.
Direct composition of reverseo + ==.

7 new tests: empty / singleton / equal pair / unequal pair /
5-element-yes / 5-element-no / strings.

389/389 cumulative.
2026-05-08 11:38:29 +00:00
7b6cb64548 mk: not-membero — relational "x is not in l"
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Mirrors the structure all-distincto already uses internally: walk the
list, ensure each element is not equal to x via nafc, recurse on tail.
Useful as a constraint-style filter:

  (membero x (list 1 2 3 4 5))
  (not-membero x (list 2 4))
    -> x in {1, 3, 5}

4 new tests, 382/382 cumulative.
2026-05-08 11:36:14 +00:00
c2b238635f mk: repeato + concato
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
repeato: produces (or recognizes) a list of n copies of a value, with n
Peano-encoded. Runs forward, backward (recover the count from a uniform
list), and bidirectionally.

concato: fold-appendo over a list-of-lists. (concato (list (list 1 2)
(list) (list 3 4 5)) q) -> ((1 2 3 4 5)).

10 new tests, 378/378 cumulative.
2026-05-08 11:34:28 +00:00
8c48a0be63 mk: tako + dropo — Peano-indexed prefix and suffix
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
(tako n l prefix) — prefix is the first n elements of l.
(dropo n l suffix) — suffix is l after dropping the first n.

Both use a Peano natural for the count. Round-trip holds:
  (tako n l) ⊕ (dropo n l) = l   (verified by an end-to-end test)

9 new tests, 368/368 cumulative.
2026-05-08 11:32:33 +00:00
54a58c704e mk: eveno + oddo — Peano parity predicates
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
eveno: zero, or (s (s m)) when m is even.
oddo:  one, or (s (s m)) when m is odd.

Both run forward (predicate test on a Peano number) and backward
(enumerate even / odd numbers). The two are mutually exclusive — no
number satisfies both.

12 new tests, 359/359 cumulative.
2026-05-08 11:30:02 +00:00
ada405b37b mk: defrel — Prolog-style relation-definition macro
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
(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.
2026-05-08 11:27:49 +00:00
99066430fd mk: lasto + init-o — last and not-last list relations
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
lasto: x is the final element of l. Direct base case (l = (x)) plus
recurse-on-cdr.

init-o: init is l without its last element. Base case for singleton:
(== init ()). Otherwise recurse, threading the head through to the
init result via conso.

Together with appendo, the round-trip init append (list last) = l
holds, which is exercised by an end-to-end test.

8 new tests, 344/344 cumulative.
2026-05-08 11:25:12 +00:00
48835f2d4f mk: relational database queries — Datalog-style demo
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
tests/rdb.sx shows the library as a small Datalog engine over fact
tables. Each table is an SX list of tuples, wrapped by a relation that
does (membero (list ...) table). Queries compose selection, projection,
and joins entirely in run* / fresh / conde / membero / intarith / nafc.

Five queries: dept filter, salary > threshold, employee-project join,
intersection (engineers on a specific project), anyone on multiple
distinct projects.

5 new tests, 336/336 cumulative.
2026-05-08 11:22:12 +00:00