;;; extKanren.knl --- Extensible Kanren -*- indent-tabs-mode:nil -*- ;;; Code: ($provide! (var empty-state succeed fail disj conj $condᵉ $fresh $run $run* state-data set-state-data ≡ nullᵒ consᵒ pairᵒ not-nullᵒ not-pairᵒ) ($import! emacs-global kernel-make-predicate) ($define! barebones? #f) ($define! include-stream-fns? #t) ($define! include-condᵃ? #t) ;; appendᵒ ($define! include-basic-goals? #t) ($when barebones? ($define! include-stream-fns? #f) ($define! include-condᵃ? #f) ($define! include-basic-goals? #f)) ;;* Support ;;** Basic utilities ($define! $quote (unwrap identity)) ($define! b-> boolean->elisp-boolean) ($define! b<- (inline ($λ (obj) (not? (null? obj))))) ($define! boolean? (inline ($λ (obj) (memq? obj (copy-es-immutable (list #f #t)))))) ;; fix/environment ($define! f/e (inline ($λ (appv &optional env) (delay-inline (eval (list $λ ($quote args) (list apply (list wrap ($quote appv)) ($quote args) ($if (null? env) (list make-environment) (list $quote env)))) ($bindings->environment (appv (unwrap appv)))))))) ($define! $let (inline ($vau (bindings . body) env ($import! (make-kernel-standard-environment) $let map) ($if (symbol? bindings) ($let ((loop-name bindings) (env* (make-environment env)) ((bindings . body) body)) ($let ((loop (eval (cons* $λ (map car bindings) body) env*))) (eval (list $define! loop-name loop) env*) ($let ((loop (delay-inline loop))) (eval (cons loop (map cadr bindings)) env)))) (eval (cons* $let bindings body) env))))) ;;** List operations ($define! fast-finite-list? (inline ($λ (ls) ($import! emacs-global (length safe-length)) ($define! drop drop-unsafe) (null? (drop (length ls) ls))))) ($define! map-nonnull-reverse (inline ($λ (f ls) ($let ((f (f/e f))) ($let loop ((rem ls) (out ())) ($let ((rem (cdr rem)) (out (cons (f (car rem)) out))) ($if (null? rem) out (loop rem out)))))))) ($define! $do-nonempty-list (inline ($vau (((name expr)) . body) env ($let ((body (delay-inline (eval (cons* $λ (list name) body) env)))) ($let loop ((rem (eval expr env))) (apply body (list (car rem))) ($let ((rem (cdr rem))) ($if (not? (null? rem)) (loop rem) #inert))))))) ($define! map-reverse (inline ($λ (f ls) ($if (null? ls) () (map-nonnull-reverse f ls))))) ($define! map-nonnull (inline ($λ (f ls) (reverse! (map-nonnull-reverse f ls))))) ($define! map (inline ($λ (f ls) ($if (null? ls) () (map-nonnull f ls))))) ($define! reduce-nonnull (inline ($λ (ls kons &optional (fsingle identity)) ($let ((kons (f/e kons))) ($let loop ((rem (cdr ls)) (acc (apply* fsingle (car ls) ()))) ($if (null? rem) acc (loop (cdr rem) (kons (car rem) acc)))))))) ($define! fold-nonnull (inline ($λ (kons knil ls) ($let ((kons (f/e kons))) ($let loop ((rem ls) (acc knil)) ($let ((rem (cdr rem)) (acc (kons (car rem) acc))) ($if (null? rem) acc (loop rem acc)))))))) ($define! fold (inline ($λ (kons knil ls) ($if (null? ls) knil (fold-nonnull kons knil ls))))) ($define! last-pair (inline ($λ (ls) ($define! drop drop-unsafe) ($define! length fast-length) (drop (- (length ls) 1) ls)))) ($define! last (inline ($λ (ls) ($define! drop drop-unsafe) (list-ref ls (- (length ls) 1))))) ;;** Value predicates ($define! flonum-zero-eq ($λ (a b) ($let ((ainf (/ 1.0 a)) (binf (/ 1.0 b))) (b-> (=? ainf binf))))) ($define! nonpair-term-eq? (inline ($λ (a b) ($define! nonpair-term-eq? ($λ (a b) ($let ((t (vector? a))) ($if t (b-> t) (b-> (elisp-equal-safe? a b)))))) (b<- (nonpair-term-eq? a b))))) ;; FIXME: assumes (in terms of performance) that a is not a kernel ;; pair; it always calls kernel-eq-proper?, which has been terribly ;; written. ($define! value-eq? (inline ($λ (a b) ($let ((t (elisp-eq? a b))) (b<- ($if t (b-> t) (elisp-pair? a) (b-> (kernel-eq-proper? a b)) (b-> (nonpair-term-eq? a b)))))))) ;; Not safe if a and b are a cons or a vector... or elisp byte code. ($define! elisp-equal-safe? (inline ($λ (a b) ($let ((t (elisp-equal? a b))) (b<- ($if t ($if (elisp-equal? a 0.0) (flonum-zero-eq a b) (b-> t)) (b-> t))))))) ($define! kernel-eq-proper? (inline ($λ (a b) ($import! emacs-global kernel-eq-proper?) (b<- (kernel-eq-proper? a b))))) ($define! exact-natural-number? (inline ($λ (obj) (b<- ($let ((x (fixnum? obj))) ($if x (b-> (>=? obj 0)) (b-> (eq? (elisp-car-safe obj) ($elisp-symbol bigpos))))))))) ($define! nonnegative-fixnum? (inline ($λ (obj) (b<- ($let ((x (fixnum? obj))) ($if x (b-> (>=? obj 0)) (b-> x))))))) ($define! positive-fixnum? (inline ($λ (obj) (b<- ($let ((x (fixnum? obj))) ($if x (b-> (>? obj 0)) (b-> x))))))) ($define! exact-rational? ($λ (obj) (b<- ($let ((x (fixnum? obj))) ($if (not? x) (b-> (memq? (elisp-car-safe obj) (copy-es-immutable (list ($elisp-symbol frac) ($elisp-symbol bigpos) ($elisp-symbol bigneg))))) (b-> x)))))) ;;* Promises ($define! promise-tag (make-uninterned-elisp-symbol "#promise#")) ;; Breaks if e.g. obj is ()... ($define! promise/fast? (inline ($λ (obj) (elisp-symbol? (car obj))))) ($define! promise? (inline ($λ (obj) (eq? promise-tag (car obj))))) ($define! $lazy (inline ($vau (expr) env (cons promise-tag (eval (list $elisp-thunk expr) env))))) ($define! force-once (inline ($λ (p) (call-elisp-thunk (cdr p))))) ($define! force (inline ($λ ($) ($if (promise? $) (force (force-once $)) $)))) ($define! lazy-goal-expr cons) ($define! force-goal-expr (inline ($λ (p) ($if (null? (cdr p)) (car p) ($let ((out (unwrap-goal (eval (car p) (cdr p))))) (set-car! p out) (set-cdr! p ()) out))))) ($define! goal-expr->g (inline ($λ (G-expr env) ($let ((g (lazy-goal-expr G-expr env))) ($λ/g (a) (call (force-goal-expr g) a)))))) ($define! goal-expr->goal (inline ($λ (G-expr env) (wrap-g (goal-expr->g G-expr env))))) ;;* Errors and checks ($define! kernel-error ($λ args ($import! emacs-global signal) (signal ($elisp-symbol kernel-error) args))) ($define! $range-error (inline ($vau (name . args) env ($import! emacs-global signal) (signal ($elisp-symbol kernel-range-error) (cons (elisp-symbol (symbol->string name)) (eval (cons list args) env)))))) ($define! not-a-pair ($λ (x) ($type-error pair? x))) ($define! not-a-fixnum ($λ (x) ($type-error fixnum? x))) ($define! not-a-boolean ($λ (x) ($type-error boolean? x))) ($define! not-a-symbol ($λ (x) ($type-error symbol? x))) ($define! not-an-applicative ($λ (x) ($type-error applicative? x))) ($define! not-a-finite-list ($λ (x) ($type-error finite-list? x))) ($define! not-a-var ($λ (x) ($type-error var? x))) ($define! not-a-goal ($λ (x) ($type-error goal? x))) ($define! not-a-stream ($λ (x) ($type-error stream? x))) ($define! not-a-state ($λ (x) ($type-error state? x))) (never-returns! not-a-pair not-a-fixnum not-a-boolean not-a-symbol not-an-applicative not-a-finite-list not-a-var not-a-goal not-a-stream not-a-state) ($define! assert-fixnum (inline ($λ (ls) ($unless (fixnum? ls) (not-a-fixnum ls)) ls))) ($define! assert-fx/natnum ($λ (obj) ($if (fixnum? obj) ($unless (>=? obj 0) ($type-error exact-natural-number? obj)) ($type-error fixnum? obj)) obj)) ($define! assert-boolean (inline ($λ (ls) ($unless (boolean? ls) (not-a-boolean ls)) ls))) ($define! assert-symbol (inline ($λ (obj) ($unless (symbol? obj) (not-a-symbol obj)) obj))) ($define! assert-applicative (inline ($λ (obj) ($unless (applicative? obj) (not-an-applicative obj)) obj))) ($define! assert-finite-list (inline ($λ (ls) ($unless (finite-list? ls) (not-a-finite-list ls)) ls))) ($define! assert-var (inline ($λ ($) ($unless (var? $) (not-a-var $)) $))) ($define! assert-pair-term (inline ($λ ($) ($unless (pair-term? $) (not-a-pair $)) $))) ($define! assert-goal (inline ($λ ($) ($unless (goal? $) (not-a-goal $)) $))) ($define! assert-stream (inline ($λ (ss) ($unless (stream? ss) (not-a-stream ss)) ss))) ($define! assert-state (inline ($λ (ss) ($unless (state? ss) (not-a-state ss)) ss))) ;;* Kanren core ;;** Terms ($define! special-term-tag ($elisp-symbol special)) ($define! special-term-tag? (inline ($λ (tag) (eq? tag special-term-tag)))) ($define! nonpair-term-tag? (inline ($λ (tag) (kernel+-type-tag? (list special-term-tag) tag)))) ($define! cons-is-kernel-pair? (inline ($λ (v) (not? (kernel-type-tag? (car v)))))) ($define! cons-is-special-term? (inline ($λ (v) (special-term-tag? (car v))))) ($define! cons-is-pair-term? (inline ($λ (v) (not? (nonpair-term-tag? (car v)))))) ($define! special-term? (inline ($λ (v) (special-term-tag? (elisp-car-safe v))))) ($define! pair-term? (inline ($λ (v) ($let ((t (elisp-pair? v))) (b<- ($if t (b-> (cons-is-pair-term? v)) (b-> t))))))) ;;** Goals and streams: types ($define! goal-tag (make-uninterned-elisp-symbol "#goal#")) ($define! state-tag (make-uninterned-elisp-symbol "#state#")) ($define! stream-tag (make-uninterned-elisp-symbol "#stream#")) ($define! bad-stream-tag (make-uninterned-elisp-symbol "#bad-stream#")) (list (set! (elisp-variable goal-tag) "goal") (set! (elisp-variable state-tag) "state") (set! (elisp-variable stream-tag) "stream") (set! (elisp-variable bad-stream-tag) "invalid-stream")) ($set! module-environment goal? (kernel-make-predicate goal-tag)) ($set! module-environment state? (kernel-make-predicate state-tag)) ($set! module-environment stream? (kernel-make-predicate stream-tag)) ;; This is the 'state' of the monad as a series-parallel state monad. ($define! state? (inline ($λ (obj) (eq? (elisp-cadr-safe obj) state-tag)))) ($define! decap-state (inline ($λ (A) (assert-state A) (decap-a A)))) ($define! encap-a (inline ($λ (a) (cons* ($elisp-symbol encap) state-tag a)))) ($define! decap-a (inline ($λ (A) (elisp-cddr A)))) ($define! make-a (inline ($λ (s data) (cons s data)))) ($define! a-subst car) ($define! a-data cdr) ($define! set-a-data (inline ($λ (a d) (make-a (a-subst a) d)))) ($define! set-a-subst (inline ($λ (a s) (make-a s (a-data a))))) ($define! state-data ($λ (A) (a-data (decap-state A)))) ($define! set-state-data ($λ (A d) (encap-a (set-a-data (decap-state A) d)))) ($define! goal? (inline ($λ (obj) (eq? (elisp-cadr-safe obj) goal-tag)))) ($define! unwrap-goal (inline ($λ (G) (assert-goal G) (unwrap-g G)))) ($define! wrap-g (inline ($λ (g) (cons* ($elisp-symbol encap) goal-tag g)))) ($define! unwrap-g (inline ($λ (G) (elisp-cddr G)))) ($define! stream? (inline ($λ (obj) (eq? (elisp-cadr-safe obj) stream-tag)))) ($define! encap-stream (inline ($λ ($) (cons* ($elisp-symbol encap) stream-tag $)))) ($define! decap-stream (inline ($λ ($) (assert-stream $) (decap-$ $)))) ($define! decap-$ (inline ($λ ($) (elisp-cddr $)))) ($define! make-invalid! (inline ($λ ($) ($unless (null? (cdr (cdr-impure $))) (set-cdr! $ (list bad-stream-tag)))))) ($define! choice cons) ($define! empty-$? null?) ($define! empty-s ()) ($define! null-data ()) ($define! unit (inline ($λ (a) (choice a mzero)))) ($define! mzero ()) ($define! empty-a (copy-es-immutable (make-a empty-s null-data))) ($define! empty-state (copy-es-immutable (encap-a empty-a))) ;; Hack: 'in already set' error ($define! empty-stream (inline ($λ () (copy-es-immutable (encap-stream mzero))))) ($set! module-environment mzero (empty-stream)) ;;** Goals: calling and construction ($define! call (inline ($λ (g a) (call-elisp-λ1 g a)))) ($define! call-encap (inline ($λ (g a) (encap-stream (call g a))))) ($define! call/G (inline ($λ (G a) (call (unwrap-g G) a)))) ($define! call/G-encap (inline ($λ (G a) (encap-stream (call/G G a))))) ($define! call/goal (inline ($λ (G a) (call (unwrap-goal G) a)))) ($define! call/goal-encap (inline ($λ (G a) (encap-stream (call/goal G a))))) ($set! module-environment call/goal ($λ (g &optional a) (call/G-encap g ($if (null? a) empty-a (decap-state a))))) ($set! module-environment $λ/g ($vau ((a-sym) . body) env (assert-symbol a-sym) ($λ/G (a) ($let ((a (encap-a a))) (decap-stream (eval-runtime-sequence body (list ($elisp-symbol env) (list env) (cons a-sym a)))))))) ($define! $λ/G (inline ($vau ((a-sym) . body) env (wrap-g (eval (cons* $elisp-λ1 a-sym body) env))))) ($define! $λ/g (inline ($vau ((a-sym) . body) env (eval (cons* $elisp-λ1 a-sym body) env)))) ;;** Variables ($define! var-tag (make-uninterned-elisp-symbol "#var#")) ($define! var-counter (make-uninterned-elisp-symbol "#var-counter#")) (list (set! (elisp-variable var-tag) "var") (set! (elisp-variable var-counter) 0)) ($define! var? (inline ($λ (obj) (eq? (elisp-cadr-safe obj) var-tag)))) ($define! var-index elisp-cddr) ($define! varsymbol (reify-var-string n)))) ($define! reify-var-string ($λ (n) ;; Bad. ($define! list->string string-append) ($if (zero? n) "_₀" ($let loop ((rem n) (out ())) ($let* (((q r) (truncate/ rem 10)) (out (cons (string-ref "₀₁₂₃₄₅₆₇₈₉" r) out))) ($if (zero? q) (list->string (cons #x5f out)) (loop q out))))))) ($set! module-environment reify reify) ($set! module-environment reify* ($λ (v a) (reify* v (decap-state a)))) ;;** Streams ($define! $-take-all (inline ($λ ($ state-mapper) ($let loop ((out ()) ($ $)) ($let (($ (force $))) ($if (empty-$? $) (reverse! out) (loop (cons (apply state-mapper (list (car $))) out) (cdr $)))))))) ($define! $-take (inline ($λ (n $ state-mapper) ($let loop ((out ()) ($ $) (n n)) ($if (zero? n) (reverse! out) ($let (($ (force $))) ($if (empty-$? $) (reverse! out) (loop (cons (apply state-mapper (list (car $))) out) (cdr $) (- n 1))))))))) ;;* Extensibility ;;** Unify ($define! custom-unifier-key (make-uninterned-elisp-symbol "#custom-unifier#")) ($define! custom-≡-processor-key (make-uninterned-elisp-symbol "#custom-≡-processor#")) (list (set! (elisp-variable custom-unifier-key) ()) (set! (elisp-variable custom-≡-processor-key) ())) ($import! emacs-global kernel-make-dynamic-binder) ($set! module-environment with-custom-unifier (kernel-make-dynamic-binder custom-unifier-key)) ($set! module-environment with-custom-≡-processor (kernel-make-dynamic-binder custom-≡-processor-key)) ($define! custom-unifier (inline ($λ () (elisp-variable custom-unifier-key)))) ($define! custom-≡-processor (inline ($λ () (elisp-variable custom-≡-processor-key)))) ;; ($define! call-unify ;; ($λ (u v a) ;; ($let ((unify-custom (custom-unifier))) ;; ($let ((s ($if (null? unify-custom) ;; (unify-s u v (a-subst a)) ;; ($let ((a (apply unify-custom (list u v (encap-a a))))) ;; ($if (eq? a #f) a ;; ;; Assume without checking, that a-data ;; ;; is still eq?. ;; (a-subst (decap-state a))))))) ;; ($if (eq? s #f) s ;; (set-a-subst a s)))))) ($define! call-unify ($λ (u v a) ($import! emacs-global copy-alist) ($let ((unify-custom (custom-unifier))) ($if (null? unify-custom) ($let* ((s-in (a-subst a)) (s (unify-s u v s-in))) ($if (eq? s #f) s (novel-s! s s-in))) ($let ((new-s (apply unify-custom (list u v (encap-a a))))) ($if (eq? new-s #f) new-s (copy-alist (check-s-alist new-s)))))))) ($define! call-≡-processor ($λ (new-s a) ($let ((s (a-subst a))) ($if (eq? s new-s) a ($let ((≡-process-custom (custom-≡-processor))) ($if (null? ≡-process-custom) (set-a-subst a (append! new-s s)) ($let ((a (apply ≡-process-custom (list new-s (encap-a a))))) ($if (eq? a #f) a (decap-state a))))))))) ($define! unify ($λ (u v a) ($let ((new-s (call-unify u v a))) ($if (eq? new-s #f) new-s (call-≡-processor new-s a))))) ($set! module-environment unify ($λ (u v A) (call-unify u v (decap-state A)))) ;;* TODO: sort! ;;** Unify ($define! unify-special (inline ($λ (U V u v s) #f))) ($define! unify-s ($λ (u v s) ($let ((u ($inline (walk u s))) (v ($inline (walk v s)))) ($if (elisp-eq? u v) s (var? u) (ext-s u v s) (var? v) (ext-s v u s) (not? (elisp-pair? u)) ($if (vector? u) #f (elisp-equal-safe? u v) s #f) (not? (cons-is-pair-term? u)) ($if (kernel-eq-proper? u v) s #f) (not? (kernel-pair? v)) #f ($let ((s (unify-s (car u) (car v) s))) ($if (not? (eq? s #f)) (unify-s (cdr u) (cdr v) s) s)))))) ($define! novel-s! ($λ (new-s s) ($if (eq? new-s s) () ($let loop ((last new-s) (rem (cdr new-s))) ($if (eq? s rem) ($sequence (set-cdr! last ()) new-s) (loop rem (cdr rem))))))) ($define! novel-s ($λ (new-s s) ($let loop ((new-s new-s) (out ())) ($if (eq? s new-s) (reverse! out) (loop (cdr new-s) (cons (car new-s) out)))))) ($define! check-s-alist ($λ (s) ($do-list ((pr (assert-finite-list s))) ($if (not? (elisp-pair? pr)) (not-a-pair pr) ($let ((key (elisp-car pr))) ($unless (var? key) ($if (kernel-type-tag? key) (not-a-pair pr) (not-a-var key)))))) s)) ;; This does NOT run the user-defined unify extensions; it's exported ;; such that one can build such extensions in the first place. ($set! module-environment unify-triangular ($λ (u v A) ($let* ((a (decap-state A)) (s-in (a-subst a)) (s (unify-s u v s-in))) ($if (eq? s #f) s (novel-s! s s-in)) ;; ($if (eq? s #f) s ;; (eq? s s-in) A ;; (encap-a (set-a-subst a s))) ))) ($set! module-environment ext-s ($λ (x v A) ($let ((a (decap-state A))) (encap-a (set-a-subst a (ext-s (assert-var x) v (a-subst a))))))) ($set! module-environment ext-s* ($λ (A alist) ($import! emacs-global copy-alist) ($let ((a (decap-state A)) (new-s (copy-alist (check-s-alist alist)))) (encap-a (set-a-subst a (append! new-s (a-subst a))))))) ;; FIXME: get this to work again. ($define! unify-cars-s ($λ (u v s) ($let ((u (walk u s)) (v (walk v s))) ($let ((u-pair? (pair-term? u)) (v-pair? (pair-term? v))) ($if u-pair? ($let ((x (car u))) ($if v-pair? ($define! y (car v)) ($define! (x y) (list (cons x (var)) v))) (unify x y s)) ($if v-pair? (unify u (cons (car v) (var)) s) ($let* ((x (var)) (s (unify (cons x (var)) u s))) ($if (eq? s #f) s (unify (cons x (var)) v s))))))))) ($define! unify-cars ($λ (u v a) ($let ((x (var)) (l (var)) (r (var))) ($unify ((a (cons x l) u a)) #f (unify (cons x r) v a))))) ($define! $unify (inline ($vau (((a-sym . unify-args)) fail-expr . body) env ($let* ((a (eval (cons unify unify-args) env))) ($if (not? (eq? a #f)) ($let ((env (make-environment env))) (eval (list $define! a-sym (list $quote a)) env) (eval (cons $sequence body) env)) ($if (eq? fail-expr #f) a (eval fail-expr env))))))) ($define! $unify-cars (inline ($vau (((a-sym . unify-args)) fail-expr . body) env ($let* ((a (eval (cons unify-cars unify-args) env))) ($if (not? (eq? a #f)) ($let ((env (make-environment env))) (eval (list $define! a-sym (list $quote a)) env) (eval (cons $sequence body) env)) ($if (eq? fail-expr #f) a (eval fail-expr env))))))) ;;** Basic goals ($define! ≡ ($λ (u v) ($λ/G (a) ($unify ((a u v a)) mzero (unit a))))) ($define! nullᵒ ($λ (v) ($λ/G (a) ($unify ((a () v a)) mzero (unit a))))) ($define! pairᵒ ($λ (v) ($λ/G (a) ($let ((v (walk v (a-subst a)))) ($if (kernel-pair? v) (unit a) mzero))))) ($define! not-nullᵒ ($λ (v) ($λ/G (a) ($let ((v (walk v (a-subst a)))) ($if ($let ((t (null? v))) (b<- ($if t (b-> t) (b-> (var? t))))) mzero (unit a)))))) ($define! not-pairᵒ ($λ (v) ($λ/G (a) ($let ((v (walk v (a-subst a)))) ($if ($let ((t (kernel-pair? v))) (b<- ($if t (b-> t) (b-> (var? t))))) mzero (unit a)))))) ($define! consᵒ ($λ (car cdr v) ($λ/G (a) ($unify ((a (cons car cdr) v a)) mzero (unit a))))) ;;** appendᵒ ($define! appendᵒ ($λ (l s* out) ($λ/G (a) ($define! loop ($λ (l out a) ($let* (($ ($unify-cars ((a l out a)) mzero ;; FIXME: this may still delay the end of the stream. ($let ((s (a-subst a))) ;; The assertions are unfortunately necessary, as ;; unify-cars can fail if we allow arbitrary ;; 'unify' implementations! ($let ((l (walk (cdr (assert-pair-term (walk l s))) s)) (out (cdr (assert-pair-term (walk out s))))) ($if (null? l) ($unify ((a s* out a)) mzero (unit a)) ($lazy (loop l out a))))))) (a ($unify ((a () l a)) #f ($unify ((a s* out a)) #f a)))) ($if (eq? a #f) ($if (empty-$? $) $ (promise/fast? $) (force-once $) $) (choice a $))))) (loop l out a)))) ;;* Public API ;;** $run ($define! run-impl ($λ (conj xs gs env) ($if (null? xs) (cons () (apply (wrap conj) gs env)) ($sequence (assert-finite-list xs) ($let* ((bindings (map-nonnull (inline ($λ (x) (assert-symbol x) (cons x (var x)))) xs)) (v0 (cdar bindings)) (env (cons* ($elisp-symbol env) (list env) bindings)) (g (apply (wrap conj) gs env))) (cons v0 (call/G g empty-a))))))) ($define! $run* ($vau (xs . gs) env ($let (((v0 . $) (run-impl conj xs gs env))) ($if (null? v0) v0 ($-take-all $ (inline ($λ (a) (reify* v0 a)))))))) ($define! $run ($vau (n xs . gs) env ($define! n (eval n env)) (assert-fx/natnum n) ($let (((v0 . $) (run-impl conj xs gs env))) ($if (null? v0) v0 ($-take n $ (inline ($λ (a) (reify* v0 a)))))))) ;;** $condᵉ ($define! $condᵉ ($vau cs env (assert-finite-list cs) ($define! clause->goal (inline ($λ (gs) (Conj-exprs gs env)))) (apply disj (map clause->goal cs)))) ;;** $condᵃ and $condᵘ ($define! $condᵃ ($vau cs env (assert-finite-list cs) ($define! process-clause (inline ($λ (exprs) (assert-finite-list exprs) ($when (null? exprs) (kernel-error "$condᵃ: clause must be a non-empty list" exprs)) ($let (((expr₁ . exprs) exprs)) (cons (lazy-goal-expr expr₁ env) ($if (null? exprs) exprs (cons exprs env))))))) ($define! $condᵃ:finish-clause (inline ($λ ($ gs) ($if (null? gs) $ (null? (cdr gs)) (bind $ (car gs)) ($let ((exprs (car-impure gs)) (env (cdr-impure gs))) (set-cdr! gs ()) ($let ((g₂ (conj-exprs exprs env))) (set-car! gs g₂) (bind $ g₂))))))) ($define! $condᵃ:loop ($λ (a $ gs cs) ($if (empty-$? $) ($if (null? cs) mzero ($let ((((g₁ . gs) . cs) cs)) ($condᵃ:loop a (call (force-goal-expr g₁) a) gs cs))) ($let ((t (null? cs))) (b<- ($if t (b-> (null? gs)) (b-> t)))) $ (promise? $) ($lazy ($condᵃ:loop a (force-once $) gs cs)) ($condᵃ:finish-clause $ gs)))) ($if (null? cs) fail ($let ((cs (map-nonnull process-clause cs))) ($λ/G (a) ($condᵃ:loop a mzero () cs)))))) ;;** $fresh ($define! $fresh ($vau (xs . gs) env ($if (null? gs) succeed (null? xs) (Conj-exprs gs env) ($sequence (assert-finite-list xs) ($let* ((bindings (map-nonnull (inline ($λ (x) (assert-symbol x) (cons x (var x)))) xs)) (env (cons* ($elisp-symbol env) (list env) bindings))) (Conj-exprs gs env)))))) ;;** Streams ($define! stream-take ($λ (n $) ($let ((out ($-take (assert-fx/natnum n) (decap-stream $) encap-a))) (make-invalid! $) out))) ($define! stream-take-all ($λ ($) ($let ((out ($-take-all (decap-stream $) encap-a))) (make-invalid! $) out))) ($define! stream-pull ($λ ($$) ($let (($ (force (decap-stream $$)))) ($if (empty-$? $) (kernel-error "An empty stream was returned") ($sequence (make-invalid! $$) (encap-a (car $))))))) ;; Destructive. ($define! stream-delays ($λ (n $) (assert-stream $) ($let ((content (cdr-impure (cdr $)))) ($if (empty-$? content) (list 0) ($sequence (make-invalid! $) ($let loop ((out ()) (n (assert-fx/natnum n)) (d 0) ($ content)) ($if (empty-$? $) (reverse! (cons d out)) (zero? n) (reverse! out) (not? (promise? $)) (loop (cons d out) (- n 1) 0 (cdr $)) ($let (($ (force-once $))) (loop out n (+ d 1) $))))))))) ;;* Optional exports ($when include-stream-fns? ($set! module-environment stream-take stream-take) ($set! module-environment stream-take-all stream-take-all) ($set! module-environment stream-pull stream-pull) ($set! module-environment stream-delays stream-delays)) ($when include-condᵃ? ($set! module-environment $condᵃ $condᵃ)) ($when include-basic-goals? ($set! module-environment appendᵒ appendᵒ)) ;;* End )