3 Second Edition
| (require typed/miniKanren/trs2) | package: miniKanren-typed |
This module implements the miniKanren language used in the second edition of The Reasoned Schemer (Friedman, Byrd, Kiselyov, and Hemann, MIT Press, 2018). The language is smaller than that of the first edition: conde is the only disjunction and it interleaves, relations are defined with defrel, and most of the implementation consists of ordinary functions whose types can be read directly.
3.1 Implementation Basis
The implementation is a Typed Racket rewrite of the official code of the second edition, https://github.com/TheReasonedSchemer2ndEd/CodeFromTheReasonedSchemer2ndEd, which is the code of Chapter 10 (“Under the Hood”) and the appendix “Connecting the Wires”.
The differences from that code are:
Types. Every definition has a type, and defrel supplies the types of the relations it defines (see defrel).
Variables. A variable is a struct compared with eq? and named after the identifier introduced by fresh, rather than a vector compared with eqv?.
Literals. Literals are compared with equal? rather than eqv?, so that two equal strings unify even when they are different objects.
Occurs check. ext-s skips the occurs check when the value is ground, which cannot create a cycle. This does not change the results; it lets the type of ext-s say that such an extension always succeeds.
Throughout this section, “Chapter n” refers to the second edition.
3.2 Types
type
type constructor
(Streamof a)
type constructor
(Suspension a)
(define-type (Streamof a) (∪ Null (Pair a (Streamof a)) (Suspension a))) (define-type (Suspension a) (→ (Streamof a)))
A stream is empty, or an answer followed by a stream (a mature stream), or a suspension that produces a stream when it is called (an immature stream). (Chapter 10.)
The three cases are distinguished by null?, pair?, and procedure?, none of which inspects an answer, so no tagging is needed to tell them apart in Typed Racket. An immature stream need not be preceded by an answer: a computation can give up control before it has produced anything. This is what lets conde switch away from a branch that never produces an answer.
type
3.3 Running Programs
> (run* q (== q '(1 2))) - : (Listof Term)
'((1 2))
> (run* (x y) (== x y)) - : (Listof Term)
'((_0 _0))
> (run 0 q succeed) - : (Listof Term)
'()
syntax
(run* q goal ...)
3.4 Goals and Relations
> (run* q (fresh (x) (== q (cons x 2)) (== x 1))) - : (Listof Term)
'((1 . 2))
> (run* q (== q (list q))) - : (Listof Term)
'()
syntax
(fresh (x ...) goal ...)
(define (name [x : Term] ...) : Goal (λ (s) (λ () ((conj goal ...) s))))
so the types of the relation are supplied by defrel itself. The inner (λ () ...) is the Suspension that makes the stream immature; this is the only place where the language suspends a computation, and therefore the only place where conde switches between branches. (Chapter 1; implemented in the appendix.)
syntax
(conde [goal ...+] ...)
> (defrel (repeato x l) (conde [(== l '())] [(fresh (d) (== l (cons x d)) (repeato x d))])) > (run 6 q (conde [(repeato 'a q)] [(repeato 'b q)])) - : (Listof Term)
'(() () (a) (b) (a a) (b b))
> (run* q (conda [(conde [(== q 1)] [(== q 2)])] [(== q 3)])) - : (Listof Term)
'(1 2)
> (run* q (condu [(conde [(== q 1)] [(== q 2)])] [(== q 3)])) - : (Listof Term)
'(1)
syntax
(project (x ...) goal ...)
> (run* q (fresh (n) (conde [(== n 3)] [(== n 4)]) (project (n) (if (exact-integer? n) (== q (* n n)) fail)))) - : (Listof Term)
'(9 16)
3.5 Substitutions and Unification
These are the operations of Chapter 10.
value
procedure
(ext-s x v s) → Substitution
x : Var v : Data s : Substitution (ext-s x v s) → (Option Substitution) x : Var v : Term s : Substitution
> (define x (var 'x)) > (ext-s x '(1 2) empty-s) - : Substitution
(list (list (var 'x) 1 2))
> (ext-s x (list 1 x) empty-s) - : (U False Substitution)
#f
procedure
v : Term s : Substitution
procedure
v : Term s : Substitution
procedure
x : Var v : Term s : Substitution
procedure
(unify u v s) → (Option Substitution)
u : Term v : Term s : Substitution
> (define x (var 'x)) > (define y (var 'y)) > (unify (cons x 2) (cons 1 y) empty-s) - : (U False Substitution)
(list (cons (var 'y) 2) (cons (var 'x) 1))
procedure
(reify-name n) → Symbol
n : Index
procedure
(reify-s v r) → Substitution
v : Term r : Substitution
procedure
(reify v) → (→ Substitution Term)
v : Term
3.6 Streams
These are the stream operations of Chapter 10. They are polymorphic in the type of answers, and together they form the stream monad: a single answer (list a) is the unit, '() is zero, append-inf is the sum, and append-map-inf is the bind.
procedure
(append-inf s-inf t-inf) → (Streamof a)
s-inf : (Streamof a) t-inf : (Streamof a)
> (take-inf #f (append-inf (λ () (list 1 2)) (list 3 4))) - : (Listof Positive-Byte)
'(3 4 1 2)
procedure
(append-map-inf g s-inf) → (Streamof b)
g : (→ a (Streamof b)) s-inf : (Streamof a)
> (take-inf 3 (append-inf (list 'a) (λ () (list 'b 'c 'd)))) - : (Listof (U 'a 'b 'c 'd))
'(a b c)