;; 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)))))))