On this page:
2.1 Implementation Basis
2.2 Types
Literal
Data
Term
Var
var
data?
Substitution
Streamof
Goal
2.3 Running Programs
run
run*
2.4 Goals
succeed
fail
==
fresh
all
alli
conde
condi
conda
condu
2.5 Substitutions and Unification
empty-s
size-s
ext-s
occurs?
walk
walk*
unify
reify-name
reify-s
reify
2.6 Streams
λG
λF
mzero
unit
choice
case∞
map∞
mplus
bind
mplusi
bindi
ife
ifi
ifa
ifu
cond-aux
all-aux
9.3

2 First Edition🔗ℹ

 (require typed/miniKanren/trs1) package: miniKanren-typed

This module implements the miniKanren language used in the first edition of The Reasoned Schemer (Friedman, Byrd, and Kiselyov, MIT Press, 2005). It keeps the operators of that edition, including the interleaving forms condi and alli and the committed-choice forms conda and condu. The types make the structure of the implementation visible: terms, substitutions, goals, and the lazy streams that carry answers all have names, and every internal operator is exported so that it can be inspected and tested in isolation.

2.1 Implementation Basis🔗ℹ

The implementation follows the one described in the first edition: unification and reification are those of Chapter 9 (“Under the Hood”), and the stream operators and goal constructors are those of the appendix “Connecting the Wires”. The second edition is a different language (see Second Edition and Comparing the Editions).

The differences from the first edition are:

  • Stream representation. The book represents “exactly one answer” by the answer itself and “an answer followed by more” by a pair whose cdr is a procedure. That union cannot be discriminated by occurrence typing for an arbitrary answer type, so here a single answer is wrapped in an immutable box and the empty stream is '(). See Streamof.

  • Occurs check. The book’s ext-s never checks for circularity; a separate checked variant is introduced in Chapter 9. Here ext-s always performs the occurs check (skipping it when the value contains no variables), so == is the checked unification and there is no separate checked variant.

  • Spelling. succeed and fail stand for the book’s #s and #u; λG and λF are typed versions of the book’s goal and thunk lambdas. Superscripts become suffixes: conde, condi, alli, ife, and so on.

  • Omissions. The helper forms that the book introduces in passing to explain a point (such as the bounded lambda of Chapter 5 and project of Chapter 9) are not part of the library.

Throughout this section, “Chapter n” refers to the first edition.

2.2 Types🔗ℹ

The type of atomic values that may appear in a term: booleans, numbers, characters, byte strings, strings, keywords, symbols, and '(). Unification compares literals with equal?.

type

Data

The type of ground terms: literals and pairs of ground terms, with no logic variables anywhere inside.

type

Term

The type of terms: literals, logic variables (Var), and pairs of terms. Every argument of == is a Term.

type

Var

struct

(struct var (name)
    #:transparent)
  name : Symbol
Logic variables. A variable is identified by the object itself (eq?), not by its name; the name is only for printing. Two calls to var with the same name produce distinct variables.

Examples:
> (define x (var 'x))
> (eq? x x)

- : Boolean

#t

> (eq? (var 'y) (var 'y))

- : Boolean

#f

procedure

(data? v) → Boolean

  v : Any
Recognizes Data, that is, terms that contain no variables.

Defined as (Listof (Pair Var Term)). A substitution is a triangular association list: a variable may be associated with a term that itself contains variables bound further along the list, and walk follows those links.

type constructor

(Streamof a)

The type of lazy streams of answers, defined as

(∪ Null
   (Boxof a)
   (Pair a (→ (Streamof a))))

A stream is empty, contains exactly one answer, or contains one answer followed by a suspended computation (a thunk) that produces the rest. The middle case is the book’s unit; the last is its choice.

type

Goal

Defined as (→ Substitution (Streamof Substitution)). A goal maps a substitution to the stream of all extensions of it under which the goal holds. A goal that fails returns the empty stream.

Examples:
> ((== 1 1) empty-s)

- : (Streamof Substitution)

'#&()

> ((== 1 2) empty-s)

- : (Streamof Substitution)

'()

2.3 Running Programs🔗ℹ

syntax

(run n (x) goal ...)

 
  n : (Option Integer)

syntax

(run* (x) goal ...)

Introduces a fresh variable x, runs the conjunction of the goals from the empty substitution, and returns a list of at most n values of x, each fully reifyd. When n is #f, all values are returned, and the computation diverges if there are infinitely many. run* is (run #f (x) goal ...). (Chapter 1.)

Unassociated variables in an answer are reified as the symbols '.0, '.1, and so on, in order of first appearance.

Examples:
> (run* (q) (== q '(1 2)))

- : (Listof Term)

'((1 2))

> (run* (q) (fresh (a b) (== q (list a b a))))

- : (Listof Term)

'((_.0 _.1 _.0))

> (run 0 (q) succeed)

- : Null

'()

2.4 Goals🔗ℹ

value

succeed : Goal

value

fail : Goal

The goal that always succeeds once and the goal that never succeeds. (Chapter 1.)

Examples:
> (run* (q) succeed)

- : (Listof Term)

'(_.0)

> (run* (q) fail)

- : (Listof Term)

'()

procedure

(== u v) → Goal

  u : Term
  v : Term
Unifies u with v (see unify). The goal succeeds once if they unify and fails otherwise. Because ext-s performs the occurs check, a variable cannot be unified with a term that properly contains it. (Chapter 1; the occurs check is discussed in Chapter 9.)

Examples:
> (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 ...)

Binds each x to a new variable and behaves as (all goal ...). The variables are created when the resulting goal is applied to a substitution, not when the fresh expression is evaluated. (Chapter 1.)

Example:
> (run* (q) (fresh (a d) (== (cons a d) q) (== a 'head)))

- : (Listof Term)

'((head . _.0))

syntax

(all goal ...)

syntax

(alli goal ...)

Conjunction. (all) and (alli) are succeed; with goals, the result succeeds for each way of satisfying every goal in sequence.

all explores the answers of the first goal one at a time: for the first answer it exhausts the rest of the conjunction before moving to the second answer. alli (“all, interleaved”) alternates between the answers produced for different answers of the earlier goals, so a later answer of the first goal is reached even when an earlier one leads to infinitely many results. (all, Chapter 6; alli, Chapter 6.)

In the examples below, the first goal chooses 'L or 'R, and counto then relates n to a list of 'Is of any length. The first choice already has infinitely many answers, so all never reaches 'R; alli alternates between the two choices.

Examples:
> (: counto (→ Term Goal))
> (define (counto n)
    (conde
      [(== n '())]
      [(fresh (m) (== n (cons 'I m)) (counto m))]))
> (run 6 (q)
    (fresh (n side)
      (all (conde [(== side 'L)] [(== side 'R)]) (counto n))
      (== q (cons side n))))

- : (Listof Term)

'((L) (L I) (L I I) (L I I I) (L I I I I) (L I I I I I))

> (run 6 (q)
    (fresh (n side)
      (alli (conde [(== side 'L)] [(== side 'R)]) (counto n))
      (== q (cons side n))))

- : (Listof Term)

'((L) (R) (L I) (R I) (L I I) (R I I))

syntax

(conde clause ...)

 
clause = [goal ...+]
  | [else goal ...]

syntax

(condi clause ...)

Disjunction. Each clause is a conjunction of goals; the result succeeds for every answer of every clause. An else clause, if present, must come last, and behaves like any other clause. With no clauses the result is fail.

conde is depth-first: it produces all the answers of the first clause before any answer of the second, so a clause with infinitely many answers hides every clause after it. condi (“cond, interleaved”) alternates between the clauses. (conde, Chapter 1; condi, Chapter 6.)

Examples:
> (: repeato (→ Term Term Goal))
> (define (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) (a a) (a a a) (a a a a) (a a a a a))

> (run 6 (q) (condi [(repeato 'a q)] [(repeato 'b q)]))

- : (Listof Term)

'(() () (a) (b) (a a) (b b))

syntax

(conda clause ...)

syntax

(condu clause ...)

Committed choice, with the same clause syntax as conde. The first goal of each clause acts as a test. The first clause whose test succeeds is chosen, and the remaining clauses are discarded.

In conda (“soft cut”), every answer of the chosen test is passed to the rest of its clause. In condu (“committed choice”), only the first answer of the test is kept. Neither form is relational in general: the result can depend on how much is already known about the variables. (Chapter 10.)

Examples:
> (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)

> (run* (q) (conda [fail] [(== q 3)]))

- : (Listof Term)

'(3)

2.5 Substitutions and Unification🔗ℹ

These are the operations of Chapter 9.

value

empty-s : Substitution

procedure

(size-s s) → Index

  s : Substitution
The empty substitution and the number of associations in a substitution.

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
Extends s with the association of x to v. If v is ground the extension always succeeds; otherwise ext-s returns #f when x occurs in v (after walking), which would make the substitution circular. The two cases are reflected in the type.

Examples:
> (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

(occurs? x v s) → Boolean

  x : Var
  v : Term
  s : Substitution
Determines whether x appears in v once every variable in v has been replaced through s.

procedure

(walk v s) → Term

  v : Term
  s : Substitution
If v is a variable associated in s, follows the chain of associations and returns the first term that is not an associated variable; otherwise returns v. Pairs are not traversed.

Examples:
> (define x (var 'x))
> (define y (var 'y))
> (walk x (list (cons x y) (cons y 'end)))

- : Term

'end

> (walk (cons x x) (list (cons x 1)))

- : Term

(cons (var 'x) (var 'x))

procedure

(walk* v s) → Term

  v : Term
  s : Substitution
Like walk, but also walks inside pairs, so the result contains only variables that s leaves unassociated.

Examples:
> (define x (var 'x))
> (walk* (cons x x) (list (cons x 1)))

- : Term

'(1 . 1)

procedure

(unify u v s) → (Option Substitution)

  u : Term
  v : Term
  s : Substitution
Returns a substitution that extends s and makes u and v equal, or #f if there is none. Variables are compared with eq?, pairs are unified component by component, and literals are compared with equal?.

Examples:
> (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))

> (unify (cons x 2) (cons 1 3) empty-s)

- : (U False Substitution)

#f

procedure

(reify-name n) → Symbol

  n : Index

procedure

(reify-s v s) → Substitution

  v : Term
  s : Substitution

procedure

(reify v) → Term

  v : Term
Reification turns the unassociated variables in an answer into readable symbols. reify-name produces the symbol for the nth variable, reify-s extends s by associating each not-yet-associated variable in v with the next name, and reify applies walk* to v with the result of (reify-s v empty-s).

Examples:
> (define x (var 'x))
> (define y (var 'y))
> (reify (list x y x))

- : Term

'(_.0 _.1 _.0)

2.6 Streams🔗ℹ

These are the operators of the appendix “Connecting the Wires”. They are exported so that goals can be written and examined at the level of streams.

syntax

(λG (s) body ...+)

syntax

(λF : a body ...+)

λG builds a Goal whose substitution argument is s. λF builds a thunk of type (→ (Streamof a)), used to suspend the rest of a stream.

syntax

(mzero)

syntax

(unit a)

syntax

(choice a f)

Constructors for the three cases of Streamof: the empty stream, the stream of exactly a, and the stream of a followed by the stream produced by the thunk f.

syntax

(case∞ e
  [() on-zero]
  [(a) on-one]
  [(a f) on-choice])
Evaluates e to a stream and dispatches on its shape, binding the single answer, or the first answer and the suspended rest.

procedure

(map∞ n p a∞) → (Listof b)

  n : (Option Integer)
  p : (→ a b)
  a∞ : (Streamof a)
Applies p to the first n answers of a∞ (all of them when n is #f), forcing only as much of the stream as needed. This is what run uses.

Example:
> (map∞ #f size-s
        ((conde [succeed] [(fresh (x) (== x 1))]) empty-s))

- : (Listof Index)

'(0 1)

procedure

(mplus a∞ f) → (Streamof a)

  a∞ : (Streamof a)
  f : (→ (Streamof a))

procedure

(bind a∞ g) → (Streamof a)

  a∞ : (Streamof a)
  g : (→ a (Streamof a))
mplus appends the stream produced by f after a∞; bind applies g to every answer of a∞ and combines the results with mplus. Together with unit and mzero they form the stream monad on which conde and all are built.

procedure

(mplusi a∞ f) → (Streamof a)

  a∞ : (Streamof a)
  f : (→ (Streamof a))

procedure

(bindi a∞ g) → (Streamof a)

  a∞ : (Streamof a)
  g : (→ a (Streamof a))
Interleaving versions of mplus and bind: after each answer, mplusi swaps the roles of its two streams. They underlie condi and alli.

syntax

(ife g0 g1 g2)

syntax

(ifi g0 g1 g2)

syntax

(ifa g0 g1 g2)

syntax

(ifu g0 g1 g2)

Two-way choices from which the cond forms are built: (cond-aux ifer clause ...) turns each clause into (ifer test (all goal ...) rest). ife and ifi behave like (conde [g0 g1] [g2]) and (condi [g0 g1] [g2]). ifa and ifu run g2 only when g0 has no answer; otherwise they pass all answers (ifa) or the first answer (ifu) of g0 to g1.

syntax

(cond-aux ifer clause ...)

syntax

(all-aux bnd goal ...)

The macros that generate the cond forms from an if-form and the conjunctions from a binding operator (bind or bindi).