Files
rose-ash/lib/minikanren/stream.sx
giles 0ba60d6a25
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
mk: phase 2A — streams + ==/conj/disj, 34 new tests
lib/minikanren/stream.sx: mzero/unit/mk-mplus/mk-bind/stream-take. Three
stream shapes (empty, mature list, immature thunk). mk-mplus suspends and
swaps on a paused-left for fair interleaving (Reasoned Schemer style).

lib/minikanren/goals.sx: succeed/fail/==/==-check + conj2/disj2 +
variadic mk-conj/mk-disj. ==-check is the opt-in occurs-checked variant.

Forced-rename note: SX has a host primitive `bind` that silently shadows
user-level defines, so all stream/goal operators are mk-prefixed. Recorded
in feedback memory.

82/82 tests cumulative (48 unify + 34 goals).
2026-05-07 19:54:43 +00:00

59 lines
1.8 KiB
Plaintext

;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions.
;;
;; Three stream shapes per The Reasoned Schemer (chapter 9):
;; mzero — empty stream (the SX empty list)
;; mature — '(s . rest) (a proper SX list of substitutions)
;; immature — a thunk (an SX lambda taking zero args, returns a stream)
;;
;; Immature thunks are how miniKanren keeps search lazy and supports
;; interleaved disjunction without diverging on left-recursive relations.
;; SX has plain function closures, so a thunk is just (fn () body).
;;
;; Names are mk-prefixed: SX has a host primitive `bind` that would shadow
;; a user-level definition.
(define mzero (list))
(define unit (fn (s) (list s)))
(define stream-pause? (fn (s) (and (not (list? s)) (callable? s))))
;; mk-mplus — interleave two streams. If the first is mature/empty we use it
;; directly; if it is paused, we suspend and swap so the other stream gets
;; explored fairly (Reasoned Schemer "interleave").
(define
mk-mplus
(fn
(s1 s2)
(cond
((empty? s1) s2)
((stream-pause? s1) (fn () (mk-mplus s2 (s1))))
(:else (cons (first s1) (mk-mplus (rest s1) s2))))))
;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing results.
(define
mk-bind
(fn
(s g)
(cond
((empty? s) mzero)
((stream-pause? s) (fn () (mk-bind (s) g)))
(:else (mk-mplus (g (first s)) (mk-bind (rest s) g))))))
;; stream-take — force up to n results out of a (possibly lazy) stream.
;; n = -1 to take all (used by run*).
(define
stream-take
(fn
(n s)
(cond
((= n 0) (list))
((empty? s) (list))
((stream-pause? s) (stream-take n (s)))
(:else
(cons
(first s)
(stream-take
(if (= n -1) -1 (- n 1))
(rest s)))))))