;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions. ;; ;; SX has no improper pairs (cons requires a list cdr), so we use a ;; tagged stream-cell shape for mature stream elements: ;; ;; stream ::= mzero empty (the SX empty list) ;; | (:s HEAD TAIL) mature cell, TAIL is a stream ;; | thunk (fn () ...) → stream when forced ;; ;; HEAD is a substitution dict. TAIL is again a stream (possibly a thunk), ;; which is what gives us laziness — mk-mplus can return a mature head with ;; a thunk in the tail, deferring the rest of the search. (define mzero (list)) (define s-cons (fn (h t) (list :s h t))) (define s-cons? (fn (s) (and (list? s) (not (empty? s)) (= (first s) :s)))) (define s-car (fn (s) (nth s 1))) (define s-cdr (fn (s) (nth s 2))) (define unit (fn (s) (s-cons s mzero))) (define stream-pause? (fn (s) (and (not (list? s)) (callable? s)))) ;; mk-mplus — interleave two streams. If s1 is paused we suspend and ;; swap (Reasoned Schemer "interleave"); otherwise mature-cons head with ;; mk-mplus of the rest. (define mk-mplus (fn (s1 s2) (cond ((empty? s1) s2) ((stream-pause? s1) (fn () (mk-mplus s2 (s1)))) (:else (s-cons (s-car s1) (mk-mplus (s-cdr s1) s2)))))) ;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing. (define mk-bind (fn (s g) (cond ((empty? s) mzero) ((stream-pause? s) (fn () (mk-bind (s) g))) (:else (mk-mplus (g (s-car s)) (mk-bind (s-cdr s) g)))))) ;; stream-take — force up to n results out of a (possibly lazy) stream ;; into a flat SX list of substitutions. n = -1 means take all. (define stream-take (fn (n s) (cond ((= n 0) (list)) ((empty? s) (list)) ((stream-pause? s) (stream-take n (s))) (:else (cons (s-car s) (stream-take (if (= n -1) -1 (- n 1)) (s-cdr s)))))))