Academia.eduAcademia.edu

cKanren miniKanren with Constraints

We present cKanren, a framework for constraint logic programming (CLP) in Scheme. cKanren subsumes miniKanren, a logic programming language embedded in Scheme. cKanren allows programmers to easily use, define, and combine different kinds of constraints. We provide two example constraint systems: one over finite domains and one over trees.

cKanren miniKanren with Constraints Claire E. Alvis Jeremiah J. Willcock Kyle M. Carter William E. Byrd Daniel P. Friedman School of Informatics and Computing, Indiana University, Bloomington, IN 47405 {calvis,jewillco,kylcarte,webyrd,dfried}@cs.indiana.edu Abstract We present cKanren, a framework for constraint logic programming (CLP) in Scheme. cKanren subsumes miniKanren, a logic programming language embedded in Scheme. cKanren allows programmers to easily use, define, and combine different kinds of constraints. We provide two example constraint systems: one over finite domains and one over trees. The cKanren framework is designed to encourage an especially pure style of logic programming in which goals can be reordered arbitrarily without affecting a program’s semantics (with an important decidability-related caveat). We develop the complete implementation of the cKanren framework, written in R6RS Scheme extended with SRFI 39 parameters. We present the implementation of cKanren’s finite domain and disequality constraint solvers, and we provide introductions to miniKanren, cKanren, and numerous example programs, including the Send More Money cryptarithmetic puzzle and N-Queens. Categories and Subject Descriptors D.1.6 [Programming Techniques]: Logic Programming; D.1.1 [Programming Techniques]: Applicative (Functional) Programming General Terms • We describe the two kinds of constraints implemented Languages Keywords CLP, CLP(FD), Scheme, miniKanren, logic programming, constraint solving, constraint logic programming 1. The cKanren framework is an extension of miniKanren, which embeds logic programming in Scheme (Friedman et al. 2005; Byrd and Friedman 2006; Byrd 2009). Unlike previous extensions to miniKanren, such as αKanren (Byrd and Friedman 2007), cKanren does not directly add user-level operators to miniKanren. Rather, cKanren allows programmers to use, create, and combine constraint systems. cKanren is implemented in R6RS Scheme (Sperber et al. 2007), and uses the SRFI-39 parameter mechanism (Feeley 2002) supported by multiple Scheme implementations (Flatt and PLT 2010; Dybvig 2010). In addition to describing the cKanren framework, we present constraints for finite domains and tree terms. Constraints over finite domains allow miniKanren programmers to declaratively reason about finite sets of values, based on a restricted set of relational arithmetic operators. Disequality constraints over tree terms allow miniKanren programmers to express a limited but useful form of negation: that two terms are not equal and can never be made equal. Our paper makes the following contributions: • Introduction We present a complete implementation of cKanren, a framework for constraint logic programming in Scheme. Traditional logic programming provides only a single constraint: equality over terms, which is implemented using unification. Constraint logic programming (Apt 2003; Jaffar and Maher 1994), also known as CLP, supports additional constraints, such as constraints over finite domains CLP(FD) and tree terms CLP(Tree). • • • • within our cKanren framework: 6fd , +fd , 6≡fd , and alldiff fd constraints over finite domains (Section 2.1.1), and 6≡ disequality constraints over tree terms (Section 2.1.2). We provide examples and exercises (Section 2.1.3) using both kinds of constraints, including the famous Send More Money and N-Queens problems, along with rember o , a program that demonstrates the need for disequality constraints. We present our solutions in Section 4. We present complete implementations of the cKanren constraint framework (Section 3.1), and constraints over finite domains (Section 3.3) and tree terms (Section 3.5).1 We demonstrate how to create new kinds of constraints by composing existing cKanren constraint systems (Section 5). We describe the “miniKanren philosophy” that informed the development of cKanren, and discuss several important design and implementation goals (Section 6). We present cKanren’s helper definitions and an updated implementation of core miniKanren in the appendices. These appendices along with the code in the body of the paper comprise a working implementation. We begin with an overview of the core miniKanren language. 1 The cKanren implementation can be downloaded from github.com/calvis/cKanren. 1 2. The Language We present three languages: miniKanren, miniKanren extended with constraints over finite domains, and miniKanren extended with tree disequality. cKanren provides a framework for writing constraints and for using those kinds of constraints within miniKanren programs. We then show the finite domain and disequality goals in action, through examples such as Send More Money, N-Queens, and rember o . 2.1 miniKanren In this section we briefly review the miniKanren language; readers already familiar with miniKanren can safely skip to Section 2.1.1, while those wishing to learn more about the language should see Byrd and Friedman (2007) (from which the first part of this section has been adapted) and Friedman et al. (2005). Our code uses the following typographic conventions. Lexical variables are in italic, forms are in boldface, and quoted symbols are in sans serif. By our convention, names of relations end with a superscript o—for example any o , which is entered as anyo. Relational operators do not follow this convention: ≡ (entered as ==), conde (entered as conde), and fresh (formerly exist). Similarly, (run5 (q) body) and (run∗ (q) body) are entered as (run 5 (q) body) and (run* (q) body), respectively. miniKanren extends Scheme with three operators: ≡, conde , and fresh. There is also run, which serves as an interface between Scheme and miniKanren, and whose value is a list. fresh, which syntactically looks like lambda, introduces into scope new lexical variables bound to new (logic) variables; ≡ unifies two terms. Thus (fresh (x y z ) (≡ x z ) (≡ 3 y)) would associate x with z and y with 3. This, however, is not a legal miniKanren program—we must wrap a run around the entire expression. (run1 (q) (fresh (x y z ) (≡ x z ) (≡ 3 y))) ⇒ ( 0 ) The value returned is a list containing the single value 0 ; we say that 0 is the reified value of the unbound variable q and thus can be any value. q also remains unbound in 1 (run (q) (fresh (x y) (≡ x q) (≡ 3 y))) ⇒ ( 0 ) We can get back other values, of course. (run1 (y) (fresh (x z ) (≡ x z ) (≡ 3 y))) (run1 (y) (run1 (q) (fresh (x y) (fresh (x z ) (≡ 4 x ) (≡ x z ) (≡ x y)) (≡ 3 z ) (≡ 3 y)) (≡ q x ))) Each of these examples returns (3); in the rightmost example, the y introduced by fresh is different from the y introduced by run. A run expression can also evaluate to the empty list. This indicates that there does not exist any value of the variable bound by the run expression that can cause its body to succeed. (run1 (x ) (≡ 4 3)) ⇒ () We use conde to get several values. Syntactically, conde looks like cond but without ⇒ or else. For example, 2 (run2 (q) (fresh (w x y) (conde ((≡ ‘(,x ,w ,x ) q) (≡ y w )) ((≡ ‘(,w ,x ,w ) q) (≡ y w ))))) ⇒ (( 0 1 0 )( 0 1 0 )) Although the two conde lines are different, the values returned are identical. This is because distinct reified unbound variables are assigned distinct subscripts, increasing from left to right—the numbering starts over again from zero within each value, which is why the reified value of x is 0 in the first value but 1 in the second value. The superscript 2 in run denotes the maximum length of the resultant list. If the superscript ∗ is used, then there is no maximum imposed. This can easily lead to infinite loops: (run∗ (q) (let loop () (conde ((≡ #f q)) ((≡ #t q)) ((loop))))) Had ∗ been replaced by a natural number n, then an n-element list of alternating #f’s and #t’s would be returned. The conde succeeds while associating q with #f, which accounts for the first value. When getting the second value, the second conde line is tried, and the association made between q and #f is forgotten—we say that q has been refreshed. In the third conde line, q is refreshed again. We now look at several interesting examples that rely on any o , which tries g an unbounded number of times. (define any o (λ (g) (conde (g) ((any o g))))) Consider the first example, (run∗ (q) (conde ((any o (≡ #f q))) ((≡ #t q)))) which does not terminate because the call to any o succeeds an unbounded number of times. If ∗ were replaced by 5, then we would get (#t #f #f #f #f). (The user should not be concerned with the order in which values are returned.) Now consider (run10 (q) (any o (conde ((≡ 1 q)) ((≡ 2 q)) ((≡ 3 q))))) ⇒ (1 2 3 1 2 3 1 2 3 1) Here the values 1, 2, and 3 are interleaved; our use of any o ensures that this sequence is repeated indefinitely. Even if some conde lines loop indefinitely, other conde lines can contribute to the values returned by a run expression. However, we are not concerned with expressions looping indefinitely. For example, (run3 (q) (let ((never o (any o (≡ #f #t)))) (conde ((≡ 1 q)) (never o ) ((conde ((≡ 2 q)) (never o ) ((≡ 3 q))))))) returns (1 2 3); replacing run3 with run4 would cause divergence since there are only three values and never o would loop indefinitely looking for the fourth. 2.1.1 miniKanren with Finite Domain Constraints Finite domain constraints allow the user to assign a finite domain to a variable and to use mathematical relations between variables such as 6, <, 6=, and +. Termination is guaranteed when programmers limit themselves to fresh, ≡, conde , and the finite domain operators, since the domains are finite (Jaffar and Maher 1994). However, this guarantee no longer holds when using recursion. We introduce five goals below, where n∗ denotes a nonempty list of strictly increasing natural numbers, x denotes a variable, u, v , and w denote arbitrary values, and v∗ denotes either a list of values or a variable eventually associated with a list of values. In finite domains, any non-variable value should be a natural number. Names that end with fd and have no subscript like the ones below comprise the user interface: domfd and four constraints. • (domfd x n∗ ) (entered as domfd) constrains x ∈ n∗ . • • • • If n∗ = (n), then (domfd x n∗ ) is the same as (≡ x n). (6fd u v ) (entered as <=fd) constrains u 6 v. (+fd u v w ) (entered as plusfd) constrains u + v = w. (6≡fd u v ) (entered as =/=fd) constrains u 6= v. (all-diff fd v∗ ) (entered as all-difffd) ensures that all variables and values found within the flat list v∗ are different from each other. The following examples illustrate these constraints. In the first example, we state that q is never the same as 2. Then, when we state that q can only be a 1, 2, or 3, we exclude 2 from being a possible value of q. (run∗ (q) (6≡fd q 2) (domfd q (1 2 3))) ⇒ (1 3) A variable’s domain can be defined at any time within the variable’s scope. We use the derived goal (a goal that is defined in terms of other goals) infd which allows for assigning a domain to several different variables. Associating a variable with more than one domain, however, assigns the intersection of those domains to the variable. (define-syntax infd (syntax-rules () (( x0 x . . . e) (let ((n∗ e)) (fresh () (domfd x0 n∗ ) (domfd x n∗ ) . . . ))))) In the next example below, when we write (≡ x y), we are stating that x is going to be the same as y in every answer, so (≡ q ‘(,x ,y ,z )) could have been (≡ q ‘(,x ,x ,z )) and (infd y ’(3 4 5)) is the same as (infd x ’(3 4 5)). (run∗ (q) (fresh (x y z ) (infd z ’(1 3 5 6 7 8)) (≡ x y) (infd y ’(3 4 5)) (≡ q ‘(,x ,y ,z )) (infd z ’(5 6 9)) (infd x ’(1 2 3)))) ⇒ ((3 3 5) (3 3 6)) Even if a variable is not bound to a constant by the end of a program (as is the case with z in the example above), the variable will be associated with any satisfiable value in the domain of that variable if it is included in the returned expression. We introduce the derived goal <fd along with the very useful function range, which, given two natural numbers, returns a list of all the numbers between lb and ub, inclusively. (define <fd (λ (u v ) (fresh () (6fd u v ) (6≡fd u v )))) (define range (λ (lb ub) (cond ((< lb ub) (cons lb (range (+ lb 1) ub))) (else (cons lb ’()))))) The following is a simple example of <fd and range in action. (run∗ (x ) (6fd x 7) (<fd 2 x ) (infd x (range 0 10))) ⇒ (3 4 5 6 7) Variables used with finite domain constraints must have domains. As a result, (run∗ (q) (fresh (x y) (<fd x y) (<fd y x ))) signals an error; an alternative would be to fail, which would be unfriendly to the user. Unsatisfiable constraints, even when the variables are not referenced or associated with the run variable in any way, still result in failure. (run∗ (q) (fresh (x y z ) (infd x y z ’(1 2)) (all-diff fd ‘(,x ,y ,z )) (≡ q 5))) ⇒ () 3 A variant of this example has a domain of three values. (run∗ (q) (fresh (x y z ) (infd x y z ’(1 2 3)) (all-diff fd ‘(,x ,y ,z )) (≡ q x ))) ⇒ (1 2 3) Here each element of x ’s domain shows up as a value. But, consider this expression. (run∗ (q) (fresh (x y z ) (infd x y z ’(1 2 3)) (all-diff fd ‘(,x ,y ,z )) (≡ q ‘(,x ,z )))) ⇒ ((1 2) (1 3) (2 1) (3 1) (2 3) (3 2)) Had (≡ q ‘(,x ,z )) been (≡ q 5), the result would have been (5), because cKanren ignores domain variables that are not associated with the variable bound by the run expression. Here is a simple example of all-diff fd . (run (q) (infd q (range 3 6)) (all-diff fd ‘(2 3 ,q))) ⇒ (4 5 6) ∗ We want values for q that satisfy the all-diff fd goal. We observe that if we choose 3, we will have two occurrences of 3, so that value is not included in the list of answers associated with q. But, if we try either 4, 5, or 6, then we observe that none of them are the same as 2 or 3. Now, consider this run∗ expression. (run∗ (q) (fresh (x y z ) (infd x y z (range 1 5)) (<fd z x ) (+fd y 2 z ) (≡ q ‘(,x ,y ,z )))) ⇒ ((4 1 3) (5 1 3) (5 2 4)) 2.1.2 miniKanren with Tree Disequality We now introduce disequality constraints using 6≡ (entered as =/=), which works on arbitrary values, using the same restrictions imposed on miniKanren. (run∗ (q) (fresh (x y) (conde ((≡ x 1) (≡ y 1)) ((≡ x 2) (≡ y 2)) ((≡ x 1) (≡ y 2)) ((≡ x 2) (≡ y 1))) (6≡ x y) (≡ q ‘(,x ,y)))) ⇒ ((1 2) (2 1)) The next example relies on all-diff o , a derived goal that uses 6≡. all-diff o takes a list and succeeds as long as all the values in the list are different at all points in the program. (define all-diff o (λ (l ) (conde ((≡ l ’())) ((fresh (a) (≡ l ‘(,a)))) ((fresh (a ad dd ) (≡ l ‘(,a ,ad . ,dd )) (6≡ a ad ) (all-diff o ‘(,a . ,dd )) (all-diff o ‘(,ad . ,dd ))))))) 4 Our final example mimics the last all-diff fd example but with an unbounded, instead of bounded, result. (run1 (q) (all-diff o ‘(2 3 ,q))) ⇒ (( 0 : (6≡ (( 0 . 2)) (( 0 . 3))))) Thus, any value for q suffices, provided it is neither 2 nor 3. This is an unbounded number of answers. Because of the imposed bound required when using all-diff fd , however, the number of answers is bounded. 2.1.3 Exercises • Finite domains can be used to solve standard cryptarith- metic problems, such as finding the correct letter values to satisfy the following equation: + SEND M ORE MONEY Each letter represents a different digit in the range 0 through 9, and the two leading digits, S and M , should be nonzero. • n queens are placed on an n × n chessboard so that no two queens can attack one another. Of course, we want to find all the solutions. • The Reasoned Schemer (Friedman et al. 2005) shows how to derive recursive relational programs from recursive functional programs. But, our approach sometimes fails to work! Consider, (define rember (λ (x ls) (cond ((null? ls) ’()) (else (let ((a (car ls)) (d (cdr ls))) (let ((res (rember x d ))) (cond ((equal? a x ) res) (else ‘(,a . ,res))))))))) and its derived recursive relation. (define rember o (λ (x ls out) (conde ((≡ ’() ls) (≡ ’() out)) ((fresh (a d res) (≡ ‘(,a . ,d ) ls) (rember o x d res) (conde ((≡ a x ) (≡ res out)) ((≡ ‘(,a . ,res) out)))))))) (run∗ (q) (rember o ’a ’(a b a c) q)) ⇒ ((b c) (b a c) (a b c) (a b a c)) Is this the correct answer? The first list removes all occurrences of a, so it seems so. But then the second list removes the first occurrence of a; the third list removes the second occurrence of a; and the last list removes no occurrences of a. So, we know the last three lists are wrong. And worse, how can the following example succeed? (run∗ (q) (rember o ’a ’(a b c) ’(a b c))) ⇒ ( 0 ) Solutions to the exercises can be found on page 12. 3. Implementation In this section, we introduce the framework that we use to implement the constraint systems. Part of understanding the framework is to get an intuitive feel for how it works in the presence of any ordering of the goals. That’s the point of Section 3.1.1. Following that, we show the specifics of each constraint system. Definitions that are neither in the body of the paper nor in R6RS are defined in the appendices. 3.1 Framework The cKanren framework has been designed to be as flexible as possible, so users can define their own constraints with ease. We implement operators for unification, constraint propagation, and satisfiability that can be extended with user-defined functions. 3.1.1 Core Data Structures All information is kept in a package a, with three different stores. First, the substitution s contains all associations directly resulting from unification. Second, d is a store for all the domains of variables. This store is a list of (var . domain) pairs, where domain is a non-empty (ordered small to large, with no duplicates) list of natural numbers. Finally, the constraint store c contains only predefined constraints that have been encountered, in normalized form. In the first two stores, s and d , each association is a variable paired with an associated value. The constraint store is not an association list; instead, it contains a list of operator constraints (described below). Although there are other approaches for organizing a package, we have chosen this way for simplicity and readability. Each kind of information is separated so the user can see organized output for debugging; retrieval functions have less information to sift through; and there is no need for dummy indicator values when variables are unassociated in s, do not have a domain in d , or are unconstrained in c. We provide a package constructor, and an accessor that lexically binds variables to each different store.2 (define make-a (λ (s d c) (cons s (cons d c)))) (define-syntax λM (syntax-rules (:) (( (a : s d c) body) (λ (a) (let ((s (car a)) (d (cadr a)) (c (cddr a))) body))) (( (a) body) (λ (a) body)))) In addition, we define two related operators. When identity M is passed as the second argument to composeM , then (composeM fM fˆM ) = fM , since for all a, (and a a) = a. Each store is initially the empty list but can be extended using specialized functions. ext-s and ext-d simply extend an association list, whereas ext-c extends the constraint store provided the new constraint has at least one variable in it. This ensures that constraints strictly between constants are not in the constraint store. Although what is contained within a constraint differs depending on which constraints are used, all constraints that reside in c look like ‘(,(opc arg . . . ) opc ,arg . . . ). Each such constraint has an operator name, opc , which is the name of the helper function called directly or indirectly from the associated constraint constructor. In programs, we use the lexical variable oc to refer to such an “operator constraint.” Here is the definition of ext-c. (define ext-c (λ (oc c) (cond ((any/var? (ocrands oc)) (cons oc c)) (else c)))) The implementor’s macro buildoc (page 15) requires that arg . . . appear twice. This imposes the same kind of let use that appears in case. We form hygienically-generated lexical variables, z . . . , leading to (let ((z arg) . . . ) ‘(,(opc z . . . ) opc ,z . . . )) which is where we set up the code to invoke a helper function, opc , and where we place the name of the helper function, opc . If the result is viewed as a dotted pair, then we get ‘(,(opc z . . . ) . (opc ,z . . . )), whose car, a function call, has been invoked and whose cdr, a list, describes the function call. 3.1.2 Watching cKanren Run We now show how three simple examples run. In the expressions below, we show each step where s, d , or c changes. In the traces of these examples s contains associations to a natural number or to a variable; d contains associations to lists of natural numbers; and c contains the constraints. For ease of reading, the stored procedure, which would have been the first item in an oc, has been deleted. Whenever any changes occur while running the constraints in c, these constraints compute a fixpoint. The goal expressions within a fresh expression are presumed to be numbered, in the first example, from 1 to 5. For our first example, we show three variants of the same program, each with the goals in a different order. By changing the order of goals on the same example, we indeed get the same answer, but more importantly, these examples demonstrate how the three stores conspire to produce the correct answer. (define composeM (λ (fM fˆM ) (λM (a) (let ((a (fM a))) (and a (fˆM a)))))) (run∗ (q) (fresh (x y z ) (infd x z (range 3 5)) (infd y (range 1 4)) (<fd x 5) (≡ x y) (≡ q ‘(,y ,z )))) ⇒ ((3 3) (4 3) (3 4) (4 4) (3 5) (4 5)) 2 Variations on λ could generate simpler code when s, d, or c is M not free in body. We propose defining those macros as an exercise for the reader. In the first two goals, we initialize the domains (see d1 and d2 , below). In the third goal, we restrict x < 5, which is the same as restricting x 6 5 and x 6= 5 [c3 ]. This removes 5 from (define identity M (λM (a) a)) 5 x ’s domain [d3 ] and revises the two constraints that comprise the derived constraint <fd (see ĉ3 ). The next step unifies x with y, recording that information in the substitution s4 . Once x and y have been unified, their domains become their common elements [d4 ]. (Once in the substitution, x loses its identity and, henceforth, it is only y. That is, each time we look up x , we find that it would be the same as looking up y. [c4 ]) Then we form all possible pairs of values of y and z . d1 ⇒ ((x . (3 4 5)) (z . (3 4 5))) d2 ⇒ ((x . (3 4 5)) (y . (1 2 3 4)) (z . (3 4 5))) fd c3 ⇒ ((6fd c x 5) (6≡c x 5)) d3 ⇒ ((x . (3 4)) (y . (1 2 3 4)) (z . (3 4 5))) ĉ3 ⇒ ((6fd c x 5)) s4 ⇒ ((x . y)) d4 ⇒ ((x . (3 4)) (y . (3 4)) (z . (3 4 5))) c4 ⇒ ((6fd c y 5)) Next, by swapping two goals, we get a slightly different view. (run∗ (q) (fresh (x y z ) (infd x z (range 3 5)) (infd y (range 1 4)) (≡ x y) (<fd x 5) (≡ q ‘(,y ,z )))) ⇒ ((3 3) (4 3) (3 4) (4 4) (3 5) (4 5)) We start out with d1 and d2 being the same as in the previous example. When we unify x and y, we extend the substitution (see s3 ), and shrink y to be those values that are common to x and y [d3 ]. Doing so virtually allows us to forget about x . We install y in the constraint that is added, observing that neither x nor y can be 5. The 6≡fd c constraint has only constants, so it never gets added to c [c3 ]. Once again, we form all possible pairs of values of y and z . d1 ⇒ ((x . (3 4 5)) (z . (3 4 5))) d2 ⇒ ((x . (3 4 5)) (y . (1 2 3 4)) (z . (3 4 5))) s3 ⇒ ((x . y)) d3 ⇒ ((x . (3 4)) (y . (3 4)) (z . (3 4 5))) c3 ⇒ ((6fd c y 5)) Finally, we move the two infd goals. (run∗ (q) (fresh (x y z ) (≡ x y) (<fd x 5) (infd z x (range 3 5)) (infd y (range 1 4)) (≡ q ‘(,y ,z )))) ⇒ ((3 3) (4 3) (3 4) (4 4) (3 5) (4 5)) We see that x is the same as y (see s1 , below). The next goal adds the two predefined constraints from the derived goal [c2 ]. Thus, the constraints use y instead of x , since x is just y. Then the first infd goal runs d3 ), placing values in the domain, but those domains must be run with respect to the constraints, which causes y to shrink [dˆ3 ]. The second infd goal runs but makes no changes to the domain, since 6 y is (3 4), a subset of (3 4 5). However, c changes by first removing (6≡fd c y 5), since every value in y’s domain differs from 5 [c4 ], and then changes c again, since every value in y’s domain is less than or equal to 5 [ĉ4 ]. As before, we form all possible pairs of y and z . s1 ⇒ ((x . y)) fd c2 ⇒ ((6fd c y 5) (6≡c y 5)) d3 ⇒ ((y . (3 4 5)) (z . (3 4 5))) dˆ3 ⇒ ((y . (3 4)) (z . (3 4 5))) c4 ⇒ ((6fd c y 5)) ĉ4 ⇒ () We next consider how all-diff fd and +fd work. (run∗ (q) (fresh (w x y z ) (infd w z (range 1 5)) (all-diff fd q) (≡ q ‘(,x ,y ,z )) (≡ ‘(,x 2) ‘(1 ,y)) (+fd x y w ) (+fd w y z ))) ⇒ ((1 2 5)) First we associate the two domains in d1 , below. Next, we run c2 , which creates a placeholder in the constraint store that acknowledges that we don’t yet know what the variable q will be associated with. On the very next step, we discover that q is associated with the list ‘(,x ,y ,z ). This is acknowledged by changing the kind of constraint (in c3 ) to all-diff/fd c , since we now know that there are two lists: the unresolved variables and the found values. At this time, we have not found any values. Step 4 changes all three stores. First, the substitution grows by two associations. Once we see that x is 1 and y is 2, then we know that z cannot be either 1 or 2, so they are dropped from z ’s domain. At the same time we have found the values of x and y, so x and y are dropped from the first list in the constraint and the associated found values are placed, sorted into the second list. In step 5 we add the constraint that states 1 + 2 = w which vanishes when we find out that 3 is in w ’s domain. A similar event happens in step c6 by adding 3 + 2 = z. It too vanishes when we find out that 5 is in z ’s domain. Just before reification, we discover that 5 is not a member of the found list of values. d1 ⇒ ((z . (1 2 3 4 5)) (w . (1 2 3 4 5))) c2 ⇒ ((all-diffcfd q)) s3 ⇒ ((q . (x y z ))) c3 ⇒ ((all-diff/fd c (z y x ) ())) s4 ⇒ ((x . 1) (y . 2) (q . (x y z )))) d4 ⇒ ((z . (3 4 5)) (w . (1 2 3 4 5))) c4 ⇒ ((all-diff/fd c (z ) (1 2))) fd c5 ⇒ ((+fd c 1 2 w ) (all-diff/c (z ) (1 2))) fd c6 ⇒ ((+fd c 3 2 z ) (all-diff/c (z ) (1 2))) ĉ6 ⇒ ((all-diff/fd c (z ) (1 2))) The last example shows how 6≡ works. (run∗ (q) (fresh (w x z ) (6≡ ‘((1 ,x ) ,q #f) ‘(,z x ,w )) (≡ z ‘(1 ,x )) (≡ w #f) (≡ q ’q))) ⇒ (q) By unifying the two values in the empty substitution, we create a substitution that means that the three associations cannot all hold (see c1 below). In s2 , we show that z is associated with (1 x ). Thus, one of the three associations that must not hold now holds. If the remaining two hold, then this expression fails. For the next step, we associate w with #f. This removes the second of the three constraints that “must not hold.” The final step associates q with the symbol q, which allows the constraint to be removed because q can never be the same as the symbol x. This accounts for the acceptable final substitution. Had the last goal expression been (≡ q ’x), however, then all three constraints would have held, leading to failure. c1 ⇒ (6≡neq ((z . (1 x )) (y . x) (w . #f))) c s2 ⇒ ((z . (1 x ))) ((w . #f) (q . x))) c2 ⇒ (6≡neq c s3 ⇒ ((w . #f) (z . (1 x ))) ((q . x))) c3 ⇒ (6≡neq c s4 ⇒ ((q . q) (w . #f) (z . (1 x ))) So far, we have sketched how substitutions, domains, and constraints work together. The remainder of this section fills in specifics. 3.2 Parameters The functions within cKanren perform correctly when the three parameters3 process-prefix , enforce-constraints, and reify-constraints are imported and given new values. Each parameter is initialized with a dummy value (page 15), so each kind of cKanren constraint must update each parameter with its own function. Informally, these functions should present the following interface: process-prefix This function is sent a prefix of the substitution, consisting of all the associations newly added after a unification. In addition, it is sent the current constraints. This can be an opportunity to rerun constraints for the variables with new associations but different constraints. enforce-constraints This function is run immediately before we reify the constraints and should accept the variable to be reified. Any checks for consistency or reorganization of the constraint store can be done here. reify-constraints This function is run as part of the reifier. It is responsible for building a Scheme data structure that represents the information in the constraint store of a package. 3 As seen in SRFI-39. The same functionality could be captured with global variables and side effects; however, parameters offer the cleanest solution. Prolog systems use modules and predicates with fixed names to allow customization of attribute hooks, rather than parameters. Assuming these functions are defined prior to runtime, cKanren lays the framework for unification, running constraints, and running entire miniKanren functions. 3.2.1 goal-construct Since most constraint operations should be deterministic, it is often necessary to wrap them in a goal that will succeed when a new package is returned successfully and fail otherwise. This function makes such wrapping easy. (See page 17 for the definition of λG , whose values are goals.) (define goal-construct (λ (fM ) (λG (a) (cond ((fM a) ⇒ unitG ) (else (mzeroG )))))) 3.2.2 ≡ ≡ unifies two arguments, u and v , and can result in three different scenarios. If unify (page 18) fails, the result is #f and causes the goal to fail. If the original substitution is returned unchanged, no additional action need occur. In the last case, we obtain new information from unification. That new information (contained in a prefix of the new substitution) is retrieved and passed to the function returned by (processprefix ) for further examination. (define ≡ (λ (u v ) (goal-construct (≡c u v )))) (define ≡c (λ (u v ) (λM (a : s d c) (cond ((unify ‘((,u . ,v )) s) ⇒ (λ (ŝ) (cond ((eq? s ŝ) a) (else (let ((p (prefix-s s ŝ)) (a (make-a ŝ d c))) (((process-prefix ) p c) a)))))) (else #f))))) (define prefix-s (λ (s ŝ) (cond ((null? s) ŝ) (else (let loop ((ŝ ŝ)) (cond ((eq? ŝ s) ’()) (else (cons (car ŝ) (loop (cdr ŝ)))))))))) (define #u (≡ #f #t)) (define #s (≡ #f #f)) 3.2.3 run-constraints When cKanren gets new information about a variable, such as an additional constraint or a more restricted domain, earlier constraints may be affected. Since we keep all constraint information in a store, we can recur through the store when such a change happens to reevaluate constraints. 7 A naı̈ve approach is to rerun every constraint in the store indiscriminately until there are no more changes in the store (i.e., until a fixpoint is reached). (define run-constraints0 (λ (x∗-ignored c) (cond ((null? c) identity M ) (else (composeM (ocproc (car c)) (run-constraints0 x∗-ignored (cdr c))))))) run-constraints0 performs well on small examples, but this approach is too costly when the constraint store grows. So, in a slightly less naı̈ve approach, the procedure receives a list of variables to look for and runs only those constraints involving those variables. any-relevant/var? (page 16) searches the constraint’s list of arguments for a variable in x∗ . (define run-constraints1 (λ (x∗ c) (cond ((null? c) identity M ) ((any-relevant/var? (ocrands (car c)) x∗ ) (composeM (ocproc (car c)) (run-constraints1 x∗ (cdr c)))) (else (run-constraints1 x∗ (cdr c)))))) Unfortunately, run-constraints1 still runs some constraints unnecessarily. Since we run a constraint each time before the recursive call, the constraint store being processed is not up to date. It is possible that a constraint in c no longer exists in the constraint store when the recursion reaches it. In run-constraints, below, once we have found a constraint that contains one or more of the variables we are looking for, we check to make sure that we still need the constraint. The current constraint store is pulled in and, if the constraint is still contained within that store, it is removed and rerun. If the current constraint is not within the current constraint store, a previous constraint has rendered it unnecessary (and it should not run again). (define run-constraints (λ (x∗ c) (cond ((null? c) identity M ) ((any-relevant/var? (ocrands (car c)) x∗ ) (composeM (rem/run (car c)) (run-constraints x∗ (cdr c)))) (else (run-constraints x∗ (cdr c)))))) (define rem/run (λ (oc) (λM (a : s d c) (cond ((memq oc c) (let ((ĉ (remq oc c))) ((ocproc oc) (make-a s d ĉ)))) (else a))))) After being run, the constraint might add itself back into the current constraint store, but only when it still has variable arguments. If all arguments are constants, the constraint is not reintroduced. 8 3.2.4 reify reify takes a variable x and then runs the two goals within (fresh () . . . ). The first goal ensures that the most meaningful answers available are sent to the second goal. The second goal pulls in a package a, then returns the value associated with x in a (along with any relevant constraints), first replacing all variables with symbols representing those entities. A constraint (proc name . rands) is relevant if both name and rands appear in the value associated with x . We call this process of turning a cKanren value into a Scheme value reification. The first cond line in the definition of reify below returns only the reified value v associated with x when the rename substitution is empty. If the rename substitution is not empty, it is used to rename the variables in v . Then, if there are no relevant constraints, the renamed value is returned. The else line returns both the reified value of x and the reified list of relevant constraints. (The use of choiceG and empty-f in reify is a subtlety to allow #f as a value, for example in (run∗ (q) (≡ #f q)) ⇒ (#f).) (define reify (λ (x ) (fresh () ((enforce-constraints) x ) (λG (a : s d c) (choiceG (let∗ ((v (walk∗ x s)) (r (reify-s v empty-s))) (cond ((null? r ) v ) (else (let ((v (walk∗ v r ))) (cond ((null? c) v ) (else (((reify-constraints) v r ) a))))))) empty-f ))))) 3.3 Finite Domain Implementation We created a domain interface powerful enough to allow users to switch domain representations without changing any of the constraint operations. Although we have chosen a simple domain—finite domains as sorted non-empty lists (with no duplicates) of natural numbers—we believe this framework would support a variety of other domains such as symbols, integers, or lists of sorted, nonoverlapping integer intervals. The user need only redefine basic functions such as intersection, difference, and the other simple functions found in Appendix A. A variable must be associated with either a finite domain or a natural number before reification, but this association can happen at any point in a cKanren program. While this restriction might cause some programs to be more verbose (for example, it might seem like the constraint (6fd x 5) should automatically bind the domain (0 1 2 3 4 5) to x ) such overloading of behavior forces a tradeoff between clean implementation and user convenience; we prefer the former. 3.3.1 The Finite Domain Parameters We now consider the parameters as broadly described in Section 3.2. Recall that to access a parameter, it is necessary to invoke the parameter as a function of zero arguments. First we define the three parameters: process-prefix FD , enforce- constraints FD , and reify-constraints FD . Once these are defined, we proceed to the specific finite domain constraints. process-prefix FD reconsiders each association that was added as a result of unification. Consider an association (x . v ), where x is a variable and v is a constant or a variable. The domain of x is intersected with (the domain of) v and all constraints involving x are rerun. Although doing so does not directly rerun constraints of v , a singleton intersection triggers the constraints of v , as can be seen in the definition of resolve-storableδ , below. (define process-prefix FD (λ (p c) (cond ((null? p) identity M ) (else (let ((x (lhs (car p))) (v (rhs (car p)))) (let ((t (composeM (run-constraints ‘(,x ) c) (process-prefix FD (cdr p) c)))) (λM (a : s d c) (cond ((getδ x d ) ⇒ (λ (δ) ((composeM (processδ v δ) t) a))) (else (t a)))))))))) processδ takes as arguments a value v and a domain δ. If v is a variable, all information is passed to update-varδ . If v is a domain value in δ, then we return a unchanged. (define processδ (λ (v δ) (λM (a) (cond ((var? v ) ((update-varδ v δ) a)) ((memv?δ v δ) a) (else #f))))) In update-varδ we intersect the two domains: the one associated with v in d and the domain passed into processδ . If the intersection is a singleton, we extend the substitution. Otherwise, we extend the domain with the intersection. If the two domains are disjoint, then we return false. (At this point, we have wrong information in d , but this is fine, since we look up variables in d only when they are not in s.) (define update-varδ (λ (x δ) (λM (a : s d c) (cond ((getδ x d ) ⇒ (λ (xδ ) (let ((i (intersectionδ xδ δ))) (cond ((null?δ i) #f) (else ((resolve-storableδ i x ) a)))))) (else ((resolve-storableδ δ x ) a)))))) (define resolve-storableδ (λ (δ x ) (λM (a : s d c) (cond ((singleton?δ δ) (let∗ ((n (singleton-elementδ δ)) (a (make-a (ext-s x n s) d c))) ((run-constraints ‘(,x ) c) a))) (else (make-a s (ext-d x δ d ) c)))))) We have chosen to extend the substitution directly, rather than using ≡ or unify. The presumption is that x has already been walk ed (page 18) in the substitution, so we can avoid that extra unification overhead. enforce-constraints FD has two purposes. The value associated with x is going to be returned as an answer, so it is desirable to associate x with a constant whenever possible. If x is unified with a variable that has domain information, each realizable domain value should be returned. It is always possible, however, that there are constrained variables that are not returned as part of the final answer. While we do not care about the exact value of every variable, we still need to be sure there exists at least one value for each variable such that all constraints are satisfied or the entire program should fail. force-ans recurs through the list of domain variables until it succeeds once; it is forced to stop by once o (page 19) and reification can occur. (define enforce-constraints FD (λ (x ) (fresh () (force-ans x ) (λG (a : s d c) (let ((bound-x∗ (map lhs d ))) (verify-all-bound c bound-x∗ ) ((once o (force-ans bound-x∗ )) a)))))) force-ans takes either a variable or a list of variables. If the variable is not associated with a constant in s but does have a domain, map-sum (page 16) attempts to associate that variable with everything in its domain. If the value is a pair, the car and cdr are searched for variables as well. Otherwise, it is already associated with a constant and it succeeds. (define force-ans (λ (x ) (λG (a : s d c) (let ((x (walk x s))) ((cond ((and (var? x ) (getδ x d )) ⇒ (map-sum (λ (v ) (≡ x v )))) ((pair? x ) (fresh () (force-ans (car x )) (force-ans (cdr x )))) (else #s)) a))))) reify-constraints FD , when invoked, produces an error, since there are variables in relevant constraints that are bound neither in s nor in d . (define reify-constraints FD (λ (m r ) (error ’reify-constraints FD "Unbound vars at end\n"))) We invoke (use FD ) in order to use constraints over finite domains. (define use FD (λ () (process-prefix process-prefix FD ) (enforce-constraints enforce-constraints FD ) (reify-constraints reify-constraints FD ))) 9 3.4 letδ and c-op Here, we present two very useful macros: letδ and c-op. letδ , an implementor’s convenience macro, lexically binds u to each argument’s current value in the substitution and uδ to each associated domain, respectively. (define-syntax letδ (syntax-rules (:) (( (s d ) ((u : uδ ) . . . ) body) (let ((u (walk u s)) . . . ) (let ((uδ (cond ((var? u) (getδ u d )) (else (makeδ ‘(,u))))) ...) body))))) c-op is a macro for defining constraint operations that uses letδ . buildoc (page 15) creates a storable representation of the constraint, and the constraint store is extended. The body is run when each argument u has a domain. (define-syntax c-op (syntax-rules (:) (( op ((u : uδ ) . . . ) body) (λM (a : s d c) (letδ (s d ) ((u : uδ ) . . . ) (let∗ ((c (ext-c (buildoc op u . . . ) c)) (a (make-a s d c))) (cond ((and uδ . . . ) (body a)) (else a)))))))) 3.4.1 The Goal Constructors There are five constraint constructors: domfd , 6fd , +fd , 6≡fd , and all-diff fd . • The goal (domfd x n∗ ) constrains x ∈ n∗ . The real work of domfd is done by processδ (page 9). (define domfd (λ (x n∗ ) ∗ (goal-construct (domfd c x n )))) (define domfd c (λ (x n∗ ) (λM (a : s d c) ((processδ (walk x s) (makeδ n∗ )) a)))) • The goal (6fd u v ) implies that min(u) 6 max(v ), filtering out ineligible domain elements from u and v . (define 6fd (λ (u v ) (goal-construct (6fd c u v )))) (define 6fd c (λ (u v ) (c-op 6fd c ((u : uδ ) (v : vδ )) (let ((umin (minδ uδ )) (vmax (maxδ vδ ))) (composeM (processδ u (copy-before (λ (u) (< vmax u)) uδ )) (processδ v (drop-before (λ (v ) (6 umin v )) vδ ))))))) 10 • The goal (+fd u v w ) has the meaning u + v = w. Thus we get two relations: min(u) + min(v ) 6 max(w ) min(w ) 6 max(u) + max(v ) (define +fd (λ (u v w ) (goal-construct (+fd c u v w )))) (define +fd c (λ (u v w ) (c-op +fd c ((u : uδ ) (v : vδ ) (w : wδ )) (let ((umin (minδ uδ )) (umax (maxδ uδ )) (vmin (minδ vδ )) (vmax (maxδ vδ )) (wmin (minδ wδ )) (wmax (maxδ wδ ))) (composeM (processδ w (range (+ umin vmin ) (+ umax vmax ))) (composeM (processδ u (range (− wmin vmax ) (− wmax vmin ))) (processδ v (range (− wmin umax ) (− wmax umin ))))))))) • The goal (6≡fd u v ) constrains u and v from having the same values. If the domains of u and v are disjoint, the constraint can never be violated and so it is ignored. Otherwise, the constraint is kept around until the domains are equal (which violates the constraint, returning false) or one domain is reduced to a singleton. Then the single element is removed from the other argument’s domain and the constraint is dropped. (define 6≡fd (λ (u v ) (goal-construct (6≡fd c u v )))) (define 6≡fd c (λ (u v ) (λM (a : s d c) (letδ (s d ) ((u : uδ ) (v : vδ )) (cond ((or (not uδ ) (not vδ )) (make-a s d (ext-c (buildoc 6≡fd c u v ) c))) ((and (singleton?δ uδ ) (singleton?δ vδ ) (= (singleton-elementδ uδ ) (singleton-elementδ vδ ))) #f) ((disjoint?δ uδ vδ ) a) (else (let∗ ((ĉ (ext-c (buildoc 6≡fd c u v ) c)) (a (make-a s d ĉ))) (cond ((singleton?δ uδ ) ((processδ v (diffδ vδ uδ )) a)) ((singleton?δ vδ ) ((processδ u (diffδ uδ vδ )) a)) (else a))))))))) • The goal (all-diff fd v∗ ) ensures all variables and values in v∗ differ. all-diff fd could have been written as a derived constraint of 6≡fd . Instead, this constraint is between a list of values (either variables or constants), where the constants are recursively excluded from each variable’s domain or the argument to all-diff fd might be a single variable, which eventually becomes such a list of values. (define all-diff fd (λ (v∗ ) ∗ (goal-construct (all-diff fd c v )))) In the first of all-diff fd c ’s two cond lines, we acknowledge that we have a variable (eventually to become a list) and we include in c a constraint which means “Keep trying until the variable has an association in the substitution.” In the second cond line, we must have a list, so we partition the list into two pieces: unresolved variables and the found values, making sure along the way that the found values are all different. If they are not different, all-diff fd c fails. (define all-diff fd c (λ (v∗ ) (λM (a : s d c) (let ((v∗ (walk v∗ s))) (cond ((var? v∗ ) (let∗ ((oc (buildoc all-diffcfd v∗ ))) (make-a s d (ext-c oc c)))) (else (let-values (((x∗ n∗ ) (partition var? v∗ ))) (let ((n∗ (list-sort < n∗ ))) (cond ((list-sorted? < n∗ ) ∗ ∗ ((all-diff/ fd c x n ) a)) (else #f)))))))))) In all-diff/ fd c , we are dealing with the unresolved variables y∗ and the found values n∗ . We move a variable y of y∗ to x∗ unless y is associated with a value in the substitution, presumably to a single valid value. Then, we don’t move y to x∗ and instead move y to n∗ , which must stay sorted. When each variable has been processed, we exclude the final n∗ from the possible choices remaining from each of the domains of x∗ . exclude-from δ (defined below) calls processδ to refine d , and potentially call run-constraints. (define all-diff/ fd c (λ (y∗ n∗ ) (λM (a : s d c) (let loop ((y∗ y∗ ) (n∗ n∗ ) (x∗ ’())) (cond ((null? y∗ ) ∗ ∗ (let∗ ((oc (buildoc all-diff/fd c x n )) (a (make-a s d (ext-c oc c)))) ((exclude-from δ (makeδ n∗ ) d x∗ ) a))) (else (let ((y (walk (car y∗ ) s))) (cond ((var? y) (loop (cdr y∗ ) n∗ (cons y x∗ ))) ((memv?δ y n∗ ) #f) (else (let ((n∗ (list-insert < y n∗ ))) (loop (cdr y∗ ) n∗ x∗ ))))))))))) (define exclude-from δ (λ (δ1 d x∗ ) (let loop ((x∗ x∗ )) (cond ((null? x∗ ) identity M ) ((getδ (car x∗ ) d ) ⇒ (λ (δ2 ) (composeM (processδ (car x∗ ) (diffδ δ2 δ1 )) (loop (cdr x∗ ))))) (else (loop (cdr x∗ ))))))) 3.5 Disequality Constraints Implementation The version of disequality defined on finite domains is not powerful enough to operate on lists, even when those lists contain finite domain values and variables alone. So, we describe a more general version of disequality constraints. This implementation uses unification to uncover exactly which associations must never be made. For example, the constraint (6≡ (x 1) (2 y)) means that x cannot be 2 when y is 1. Conveniently, unification of (x 1) and (2 y) produces exactly the normalized associations that should never be true simultaneously: ((x . 2) (y . 1)). This idea has been formalized by Hubert Comon (1991), who has described this and other approaches to disequality. 3.5.1 The Disequality Constraints Parameters We define the same constraint parameters as in Section 3.3.1; however, the disequality constraint parameters are much simpler than those of finite domains. process-prefix NEQ is quite simple, as no domain information is stored. The constraints of every variable within the prefix p are reexamined using run-constraints. (define process-prefix NEQ (λ (p c) (run-constraints (recover/vars p) c))) Next we consider enforce-constraints NEQ . Disequality constraints fail immediately if they are unsatisfiable. It is not necessary to check the store again before reification, so enforce-constraints is merely unitG . (define enforce-constraints NEQ (λ (x ) unitG )) reify-constraints NEQ reifies every relevant constraint in c, since they are included as part of the reified value; we have arbitrarily chosen the colon ‘:’ to separate the reified value from the list of reified constraints. (define reify-constraints NEQ (λ (m r ) (λG (a : s d c) (let∗ ((c (walk∗ c r )) (p∗ (remp any/var? (map ocprefix c)))) (cond ((null? p∗ ) m) (else ‘(,m : . ((6≡ . ,p∗ ))))))))) We invoke (use NEQ ) in order to use disequality constraints over term trees, (define use NEQ (λ () (process-prefix process-prefix NEQ ) (enforce-constraints enforce-constraints NEQ ) (reify-constraints reify-constraints NEQ ))) 11 3.5.2 The Disequality Goal We define 6≡ for disequality, which takes two arguments that must be different. If not, the goal fails. (define 6≡ (λ (u v ) (goal-construct (6≡c u v )))) (define 6≡c (λ (u v ) (λM (a : s d c) (cond ((unify ‘((,u . ,v )) s) ⇒ (λ (ŝ) ((6≡neq (prefix-s s ŝ)) a))) c (else a))))) 6≡neq operates on a substitution p. If every association c in p is in s, the disequality constraint has been violated. Otherwise, we get a new non-empty prefix to be stored. When the initial substitution is empty, ŝ is the prefix. (define 6≡neq c (λ (p) (λM (a : s d c) (cond ((unify p s) ⇒ (λ (ŝ) (let ((p (prefix-s s ŝ))) (cond ((null? p) #f) (else ((normalize-store p) a)))))) (else a))))) If unify does not return false, there is still a possibility the disequality constraint can be violated. Now ŝ contains the prefix p that must not become realized. normalize-store removes any superfluous disequality constraints in c. If p is subsumed by any prefix in c, or if p subsumes any prefix in c, there is redundancy. After the constraint store has been examined, either a new package with potentially fewer constraints or the original package with at most one more constraint is returned. (define normalize-store (λ (p) (λM (a : s d c) (let loop ((c c) (ĉ ’())) (cond ((null? c) (let ((ĉ (ext-c (buildoc 6≡neq p) ĉ))) c (make-a s d ĉ))) ((eq? (ocrator (car c)) ’6≡neq c ) (let∗ ((oc (car c)) (p̂ (ocprefix oc))) (cond ((subsumes? p̂ p) a) ((subsumes? p p̂) (loop (cdr c) ĉ)) (else (loop (cdr c) (cons oc ĉ)))))) (else (loop (cdr c) (cons (car c) ĉ)))))))) The substitution p subsumes (page 17) s if unifying p in the substitution s does not extend s. If any prefix in c subsumes p, then c is not extended. Furthermore, because p will be part of the new c, those prefixes in c that p subsumes are dropped. 12 4. Solutions to Exercises Recall from Section 2.1.3 that we left unsolved three problems: Send More Money, N-Queens, and rember o . We start with the solution to Send More Money. 4.1 send-more-money o : Solution In send-more-money o below, we must be certain that each letter in the puzzle is associated with a different value. Since we know that s and m cannot be 0 (the leading digit of a natural number is never 0), their domains start at 1. The domains of the remaining letters include 0. There are also three carry variables. (There should have been four, one for each column, but we know that m must be 1, since the sum of two digits, even with a carry-in of 1, cannot exceed 19.) Thus, we use m as the fourth carry variable.). The rest of send-more-money o does the long addition. add-digits o performs one step of long addition, adding two digits together considering a possible carry-in, and returning the sum with a possible carry-out. (define send-more-money o (λ (letters) (fresh (s e n d m o r y carry0 carry1 carry2 ) (≡ letters ‘(,s ,e ,n ,d ,m ,o ,r ,y)) (all-diff fd letters) (infd s m (range 1 9)) (infd e n d o r y (range 0 9)) (infd carry0 carry1 carry2 (range 0 1)) (add-digits o s m carry2 m o) (add-digits o e o carry1 carry2 n) (add-digits o n r carry0 carry1 e) (add-digits o d e 0 carry0 y)))) (define add-digits o (λ (augend addend carry-in carry-out digit) (fresh (partial-sum sum) (infd partial-sum (range 0 18)) (infd sum (range 0 19)) (+fd augend addend partial-sum) (+fd partial-sum carry-in sum) (conde ((<fd 9 sum) (≡ carry-out 1) (+fd digit 10 sum)) ((6fd sum 9) (≡ carry-out 0) (≡ digit sum)))))) (run∗ (q) (send-more-money o q)) ⇒ ((9 5 6 7 1 0 8 2)) This answer corresponds to the variable assignments: S=9 M =1 E=5 O=0 N =6 D=7 R=8 Y =2 which indeed satisfy the Send More Money’ puzzle. There are two interesting facets to this code. First, careful inspection of send-more-money o and add-digits o reveals that the solution requires no explicit recursion. Second, the goals of send-more-money o are ordered so that the first and second arguments to the four goals using add-digits o when read from top to bottom spell s e n d and m o r e, respectively. 4.2 n-queens o : Solution Here we solve N-Queens puzzle. Recall that in chess, a queen is attacking a piece if they are on the same diagonal, the same row, or the same column, with no interference. We define n-queens o recursively. A loop executes n times, creating n variables, one for each row. When the loop reaches its base case, the list l contains the newly initialized variables. Each variable is in a different position in the list l (since no two queens can occupy the same row); the values that will be associated with these variables must be all different (since no two queens can occupy the same column); and no two queens can share the same diagonal. all-diff fd and diagonals o enforce these last two restrictions. (define n-queens o (λ (q∗ n) (let loop ((i n) (l ’())) (cond ((zero? i) (fresh () (all-diff fd l ) (diagonals o n l ) (≡ q∗ l ))) (else (fresh (x ) (infd x (range 1 n)) (loop (− i 1) (cons x l )))))))) (define diagonals o (λ (n r ) (let loop ((r r ) (i 0) (s (cdr r )) (j 1)) (cond ((or (null? r ) (null? (cdr r ))) #s) ((null? s) (loop (cdr r ) (+ i 1) (cddr r ) (+ i 2))) (else (let ((qi (car r )) (qj (car s))) (fresh () (diag o qi qj (− j i) (range 0 (∗ 2 n))) (loop r i (cdr s) (+ j 1))))))))) In diagonals o , i keeps track of the position in the list of queens that we are on and j keeps track of the position in the list of queens somewhere to the right of i. qi and qj are two elements of n. Effectively, we check every possible position for two queens exactly once, as qi will always be less than qj . For each combination, diag o is called. As long as the following equations hold (Schrijvers et al. 2009), the queens will not be attacking each other and diag o will succeed. qi + d 6= qj qj + d 6= qi o (define diag (λ (qi qj d rng) (fresh (qi +d qj +d ) (infd qi +d qj +d rng) (+fd qi d qi +d ) (6≡fd qi +d qj ) (+fd qj d qj +d ) (6≡fd qj +d qi )))) The following expression produces the solution when n = 8. The run∗ expression evaluates to the list of 92 solutions. (length (run∗ (q) (n-queens o q 8))) ⇒ 92 4.3 rember o : Solution Recall that rember o does not give us the results we were expecting. A simple fix is that once we have found an a, we demand that we won’t get any more answers from that a as is shown in the last line of the definition of rember o , below. This concept becomes particularly important when using a representation of environments where shadowing exists, say, in a type inferencer being used as a type inhabiter for demonstrating the Curry-Howard Isomorphism. (define rember o (λ (x ls out) (conde ((≡ ’() ls) (≡ ’() out)) ((fresh (a d res) (≡ ‘(,a . ,d ) ls) (rember o x d res) (conde ((≡ a x ) (≡ res out)) ((6≡ a x ) (≡ ‘(,a . ,res) out)))))))) (run∗ (q) (rember o ’a ’(a b a c) q)) ⇒ ((b c)) (run∗ (q) (rember o ’a ’(a b c) ’(a b c))) ⇒ () 5. Composition In order to compose different kinds of constraints, it is enough to redefine each parameter according to the semantics desired by the user. For instance, we can combine the finite domain and disequality constraints by allowing 6≡ to call 6≡fd in the case of natural number arguments, or using domain information to eliminate parts of the prefix in 6≡neq c . The composite library imports the function definitions from the finite domain and disequality libraries, and defines the composite parameters in terms of the domain-specific functions. With the composite library, examples such as the following can be solved. (run∗ (q) (infd q (2 3 4)) (all-diff o ‘(apple 3 ,q))) ⇒ (2 4) Although the two kinds of constraints do not explicitly communicate, q’s domain information can be used in conjunction with all-diff o . Before reification, q will be unified with all remaining values in its domain. q’s only realizable values are 2 and 4, since all-diff o fails when 3 is tried. (Using all-diff fd instead of all-diff o would result in an error, since apple is a symbol rather than a natural number.) Conflicting constraints cannot be present in the same call to run∗ . Having two kinds of constraints in any environment would cause conflicting parameter definitions. For example, assume we wish to verify that the 92 answers returned by (run∗ (q) (n-queens o q 8)) are actually all unique answers. We cannot use all-diff fd , since the result is a list of lists, but finite domain constraints only work on a list of natural numbers, so, we must use all-diff o . (define answers (run∗ (q) (n-queens o q 8))) (run∗ (q) (all-diff o answers)) ⇒ ( 0 ) These calls to run∗ leave no opportunity to redefine parameters, so we must be sure that the definitions for all parameters work correctly when either finite domain or disequality constraints are used. Combining different kinds of constraints that use different domain representations is more complex. Conveniently, the user has the power to redefine, extend, or ignore any part of the existing libraries when making a composition. A tagged domain store could be implemented simply by redefining 13 getδ and ext-d . Basic parts of the implementation framework, like reify, are general and powerful enough to handle a new kind of constraint, provided there is a reify-constraints parameter to determine what should be returned. Each library should have a function devoted to defining the parameters properly. For finite domains it is use FD , for disequality constraints it is use NEQ , and for their composition it might be use FDNEQ . Thus, it is possible to import libraries without worrying about the values of the parameters: simply invoke the correct thunk and the definitions will be there. 6. The miniKanren Philosophy Our development of cKanren has been informed by a design and implementation philosophy that arose from our work on miniKanren. The central tenet of this philosophy is that in a purely declarative miniKanren relation, the order of goals is unimportant. That is, swapping two conjuncts (or two disjuncts) should not affect the semantics of the program. This is true only to a point: a miniKanren query that has no answers may diverge instead of failing in finite time. For a query that produces answers, however, reordering subgoals should not affect the set of possible answers returned.4 6.0.1 Design Philosophy A consequence of our philosophy is that the programmer can specify constraints in any order. For example, the finite domain constraints presented in Section 2.1.1 can be applied to a variable, even before the variable has been associated with a finite domain or has been bound in the substitution. As pointed out on page 8, provided a variable is associated with either a domain or a natural number before reification, the program works correctly. Another consequence of the miniKanren philosophy is that “extra-logical” operators, such as Prolog’s var/1, is, or “cut” (!) operators, are not allowed in user-level code. Although not enforced by cKanren, we hope that implementors of additional constraint libraries will adhere to this philosophy. As a result, our solution to Send More Money in Section 4.1 does not make assumptions about when variables become associated with domains or values. Although a less pure implementation of Send More Money might run faster, we are convinced that the benefits of declarativeness are too important to abandon. 6.0.2 Implementation Philosophy The miniKanren philosophy also guides our implementation of cKanren. For example, we decided to store a closure indicating progress along with a first-order representation of constraints in the constraint store. Keeping only a closure would have severely reduced our ability to recover information from the constraint store. A user trying to debug would not have been able to look at a textual version of the constraints. This would make it impossible to see which constraints were actually being stored. Also, an implementor trying to reify the constraints would not have been able to include meaningful information about the constraints on a variable. Instead, we have included both a symbol representing the constraint and its operands, thus giving us the best of both worlds. 4 See (Byrd 2009) for a detailed discussion of these issues and of the miniKanren design philosophy. 14 cKanren’s constraint store is a list of operator constraints. Another way to design the constraint store would be to associate a variable with its relevant constraints. However, a constraint would then appear once for each variable it references, resulting in redundancy and complicating the fixpoint algorithm. With our cKanren framework we can decide whether a constraint is still relevant with a single call to memq; the alternative approach would require more lookups to determine the same information. We had originally implemented cKanren using attributed variables (Holzbaur 1992). An attributed variable is one which calls a user-defined customization point (a “hook”) when unified, either with a value or another variable. A key reason we have avoided attributed variables is the change required to unification. Attribute hooks are called whenever an attributed variable is unified with anything, requiring the ability to call cKanren code from ≡, possibly leading to further unifications and backtracking. Although Prolog systems with attributed variables do allow backtracking from attribute hooks, we have chosen to not use attributed variables to simplify cKanren’s implementation. 7. Conclusion The field of Constraint Logic Programming is well developed and its history, along with the more general area of Constraint Programming, is described in (Marriott and Stuckey 1998) and (Apt 2003). Extensions to Prolog for Constraint Logic Programming, such as Prolog II (Colmerauer 1985), presented disequality constraints over trees. The first theoretical work on CLP(X), for different X s, appears in Jaffar and Lassez (1987). The CLP language CHIP (Dincbas et al. 1988; Van Hentenryck 1989) introduced finite domain constraints. Jaffar (1992) described constraints over real numbers (CLP(R)). CHIP led to ECLi PSe (Wallace et al. 1997). In the theory of CLP(X), the substitution store associated with logic programming becomes a set of constraints (Jaffar and Lassez 1987). Jaffar and Maher (1994) survey the development of Constraint Logic Programming. We have presented cKanren, a simple, concise, and easily extendable framework for constraint logic programming in Scheme. cKanren allows programmers to use and define constraint libraries that extend miniKanren, which itself embeds logic programming in Scheme. We have also presented the implementation of two libraries of constraints: one over finite domains, and the other over tree terms. We hope others will create cKanren libraries of their own, increasing the expressive power of miniKanren. Acknowledgments We are grateful to our readers: Adam Foltzer, Lindsey Kuper, Karissa McKelvey, David Nolen, Zach Sparks, and Cameron Swords. The early work on miniKanren would not have been successful without the insights of Mitchell Wand and Steve Ganz. Oleg Kiselyov made major improvements to every aspect of miniKanren, and we are grateful to Chung-Chieh Shan for his work on the final implementation of miniKanren. We continue to appreciate SLATEX and its creator, Dorai Sitaram. References Krzysztof R. Apt. Principles of Constraint Programming. Cambridge University Press, 2003. F. Baader and W. Snyder. Unification theory. In A. Robinson and A. Voronkov, editors, Handbook of Automated Reasoning, volume I, chapter 8, pages 445–532. Elsevier Science, 2001. URL citeseer.ist.psu.edu/ baader99unification.html. William E. Byrd. Relational Programming in miniKanren: Techniques, Applications, and Implementations. PhD thesis, Indiana University, 2009. William E. Byrd and Daniel P. Friedman. From variadic functions to variadic relations: A miniKanren perspective. In Robby Findler, editor, Proceedings of the 2006 Scheme and Functional Programming Workshop, University of Chicago Technical Report TR-2006-06, pages 105–117, 2006. William E. Byrd and Daniel P. Friedman. αKanren: A fresh name in nominal logic programming. In Proceedings of the 2007 Workshop on Scheme and Functional Programming, Universite Laval Technical Report DIUL-RT-0701, pages 79–90 (see also http://www.cs.indiana.edu/∼webyrd for improvements), 2007. Alain Colmerauer. Prolog in 10 figures. Commun. ACM, 28 (12):1296–1310, 1985. ISSN 0001-0782. Hubert Comon. Disunification: A survey. In Computational Logic – Essays in Honor of Alan Robinson, pages 322–359, 1991. URL citeseer.ist.psu.edu/ comon91disunification.html. Mehmet Dincbas, Pascal Van Hentenryck, Helmut Simonis, Abderrahmane Aggoun, Thomas Graf, and Franoise Berthier. The constraint logic programming language CHIP. In International Conference on Fifth Generation Computer Systems, pages 693–702, 1988. R. Kent Dybvig. Chez Scheme Version 8 User’s Guide. Cadence Research Systems, 2010. Marc Feeley. SRFI 39: Parameter objects. http://srfi. schemers.org/srfi-39/, 2002. Matthew Flatt and PLT. Reference: Racket. Technical Report PLT-TR-2010-1, PLT Inc., 2010. http:// racket-lang.org/tr1/. Daniel P. Friedman, William E. Byrd, and Oleg Kiselyov. The Reasoned Schemer. The MIT Press, Cambridge, MA, 2005. Ralf Hinze. Deriving backtracking monad transformers. In Proceedings of the Fifth ACM SIGPLAN International Conference on Functional Programming, ICFP ’00, Montreal, Canada, September 18–21, 2000, pages 186–197. ACM Press, 2000. Christian Holzbaur. Metastructures versus attributed variables in the context of extensible unification. In Proceedings of the 4th International Symposium on Programming Language Implementation and Logic Programming, pages 260–268, London, UK, 1992. Springer-Verlag. ISBN 3-540-55844-6. URL http://portal.acm.org/citation. cfm?id=646448.692447. J. Jaffar and J.-L. Lassez. Constraint logic programming. In Conference Record of the Fourteenth Annual ACM Symposium on Principles of Programming Languages, pages 111–119. ACM Press, 1987. Joxan Jaffar and Michael J. Maher. Constraint logic programming: A survey. J. Log. Program, 19/20:503–581, 1994. Joxan Jaffar, Spiro Michaylov, Peter J. Stuckey, and Roland H. C. Yap. The CLP(R) language and system. ACM Trans. Program. Lang. Syst., 14:339–395, May 1992. ISSN 0164-0925. URL http://doi.acm.org/10.1145/129393. 129398. David B. MacQueen, Philip Wadler, and Walid Taha. How to add laziness to a strict language without even being odd. In Proceedings of the 1998 ACM Workshop on ML, pages 24–30, September 1998. Baltimore, MD. Kim Marriott and Peter J. Stuckey. Programming with Constraints. An Introduction. The MIT Press, 1998. Eugenio Moggi. Notions of computation and monads. Information and Computation, 93(1):55–92, 1991. Tom Schrijvers, Peter Stuckey, and Philip Wadler. Monadic constraint programming. ACM Computing Surveys, 19 (6):663–697, 2009. Michael Sperber, R. Kent Dybvig, Matthew Flatt, and Anton van Straaten (eds.). Revised6 report on the algorithmic language Scheme, September 2007. URL http: //www.r6rs.org/. Pascal Van Hentenryck. Constraint Satisfaction in Logic Programming. MIT Press, 1989. Philip Wadler. How to replace failure by a list of successes: A method for exception handling, backtracking, and pattern matching in lazy functional languages. In JeanPierre Jouannaud, editor, Proceedings of the Second Conference on Functional Programming Languages and Computer Architecture, volume 201 of Lecture Notes in Computer Science, pages 113–128, Nancy, France, September 16–19, 1985. Springer-Verlag. Philip Wadler. The essence of functional programming. In Conference Record of the Nineteenth ACM SIGPLANSIGACT Symposium on Principles of Programming Languages, pages 1–14, Albuquerque, New Mexico, January, 1992. ACM Press. Mark Wallace, Stefano Novello, and Joachim Schimpf. ECLiPSe: A platform for constraint logic programming. ICL Systems Journal, 12(1):159–200, May 1997. A. cKanren Implementation Helpers (define (define (define (define (define (define (define (define (define (define (define (define process-prefix (make-parameter ’dummy)) enforce-constraints (make-parameter ’dummy)) reify-constraints (make-parameter ’dummy)) empty-d ’()) empty-c ’()) empty-a (make-a empty-s empty-d empty-c)) ext-d (λ (x fd d ) (cons ‘(,x . ,fd ) d ))) makeδ (λ (n∗ ) n∗ )) ocproc (λ (oc) (car oc))) ocrator (λ (oc) (car (cdr oc)))) ocrands (λ (oc) (cdr (cdr oc)))) ocprefix (λ (oc) (car (ocrands oc)))) (define-syntax buildoc (syntax-rules () (( op arg . . . ) (build-auxoc op (arg . . . ) () (arg . . . ))))) (define-syntax build-auxoc (syntax-rules () (( op () (z . . . ) (arg . . . )) (let ((z arg) . . . ) ‘(,(op z . . . ) . (op ,z . . . )))) (( op (arg0 arg . . . ) (z . . . ) args) (build-auxoc op (arg . . . ) (z . . . q) args)))) 15 (define list-sorted? (λ (pred ls) (cond ((or (null? ls) (null? (cdr ls))) #t) ((pred (car ls) (cadr ls)) (list-sorted? pred (cdr ls))) (else #f)))) (define list-insert (λ (pred x ls) (cond ((null? ls) (cons x ’())) ((pred x (car ls)) (cons x ls)) (else (cons (car ls) (list-insert pred x (cdr ls))))))) (define copy-before (λ (pred δ) (cond ((null? δ) ’()) ((pred (car δ)) ’()) (else (cons (car δ) (copy-before pred (cdr δ))))))) (define drop-before (λ (pred δ) (cond ((null? δ) ’()) ((pred (car δ)) δ) (else (drop-before pred (cdr δ)))))) (define map-sum (λ (f ) (letrec ((loop (λ (ls) (cond ((null? ls) #u) (else (conde ((f (car ls))) ((loop (cdr ls))))))))) loop))) getδ looks up a variable’s current domain in d . If a variable does not currently have a domain, this function returns false. In order to distinguish between variables without domains, and values that can never have domains (such as #t or negative numbers), the argument to getδ must be a variable. (define getδ (λ (x d ) (cond ((assq x d ) ⇒ rhs) (else #f)))) Applying conventional operations to domains is efficient when the lists representing the domains are sorted. (define value?δ (λ (v ) (and (integer? v ) (6 0 v )))) (define memv?δ (λ (v δ) (and (value?δ v ) (memv v δ)))) (define null?δ (λ (δ) (null? δ))) (define singleton?δ (λ (δ) (null? (cdr δ)))) (define singleton-elementδ (λ (δ) (car δ))) (define minδ (λ (δ) (car δ))) (define maxδ (λ (δ) (cond ((null? (cdr δ)) (car δ)) (else (maxδ (cdr δ)))))) 16 (define disjoint?δ (λ (δ1 δ2 ) (cond ((or (null? δ1 ) (null? δ2 )) #t) ((= (car δ1 ) (car δ2 )) #f) ((< (car δ1 ) (car δ2 )) (disjoint?δ (cdr δ1 ) δ2 )) (else (disjoint?δ δ1 (cdr δ2 )))))) (define diffδ (λ (δ1 δ2 ) (cond ((or (null? δ1 ) (null? δ2 )) δ1 ) ((= (car δ1 ) (car δ2 )) (diffδ (cdr δ1 ) (cdr δ2 ))) ((< (car δ1 ) (car δ2 )) (cons (car δ1 ) (diffδ (cdr δ1 ) δ2 ))) (else (diffδ δ1 (cdr δ2 )))))) (define intersectionδ (λ (δ1 δ2 ) (cond ((or (null? δ1 ) (null? δ2 )) ’()) ((= (car δ1 ) (car δ2 )) (cons (car δ1 ) (intersectionδ (cdr δ1 ) (cdr δ2 )))) ((< (car δ1 ) (car δ2 )) (intersectionδ (cdr δ1 ) δ2 )) (else (intersectionδ δ1 (cdr δ2 )))))) Since x∗ is a list of variables, which is constructed using var, which is itself, constructed using vector, we must use memq, when checking for membership in the list. In a purely functional setting, we would need to build variables in a different way, probably relying on a monotonically increasing non-negative integer variable. (define any/var? (λ (t) (cond ((var? t) #t) ((pair? t) (or (any/var? (car t)) (any/var? (cdr t)))) (else #f)))) (define any-relevant/var? (λ (t x∗ ) (cond ((var? t) (memq t x∗ )) ((pair? t) (or (any-relevant/var? (car t) x∗ ) (any-relevant/var? (cdr t) x∗ ))) (else #f)))) (define recover/vars (λ (p) (cond ((null? p) ’()) (else (let ((x (lhs (car p))) (v (rhs (car p))) (r (recover/vars (cdr p)))) (cond ((var? v ) (ext/vars v (ext/vars x r ))) (else (ext/vars x r )))))))) (define ext/vars (λ (x r ) (cond ((memq x r ) r ) (else (cons x r ))))) (define verify-all-bound (λ (c bound-x∗ ) (unless (null? c) (cond ((find (λ (x ) (not (memq x bound-x∗ ))) (filter var? (ocrands (car c)))) ⇒ (λ (x ) (error ’verify-all-bound "Constrained variable ˜s without domain" x ))) (else (verify-all-bound (cdr c) bound-x∗ )))))) (define subsumes? (λ (p s) (cond ((unify p s) ⇒ (λ (ŝ) (eq? s ŝ))) (else #f)))) B. miniKanren Implementation Our miniKanren implementation comprises three kinds of operators: the interface operator run; goal constructors ≡, conde , and fresh, which take a package implicitly; and functions such as ≡c (page 7), which take a package explicitly. A goal g is a function that maps a package a to an ordered sequence a∞ of zero or more packages. (For clarity, we notate λ as λG when creating such a function g.) (define-syntax λG (syntax-rules (:) (( (a : s d c) body) (λ (a) (let ((s (car a)) (d (cadr a)) (c (cddr a))) body))) (( (a) body) (λ (a) body)))) Because a sequence of packages may be infinite, we represent it not as a list but as an a∞ , a special kind of stream that can contain either zero, one, or more packages (Hinze 2000; Wadler 1985). We use #f to represent the empty stream of packages. If a is a package, then a itself represents the stream containing just a. (define mzeroG (λ () #f)) (define unitG (λG (a) a)) (define choiceG (λ (a f ) (cons a f ))) To represent a stream containing multiple packages, we use (choiceG a f ), where a is the first package in the stream, and where f is a thunk that, when invoked, produces the remainder of the stream. (For clarity, we notate λ as λF when creating such a function f .) To represent an incomplete stream, we use (inc e), where e is an expression that evaluates to an a∞ —thus inc creates an f . (define-syntax λF (syntax-rules () (( () e) (λ () e)))) (define-syntax inc (syntax-rules () (( e) (λF () e)))) (define empty-f (λF () (mzeroG ))) A singleton stream a is the same as (choiceG a empty-f ). For goals that return only a single package, however, using this special representation of a singleton stream avoids the cost of unnecessarily building and taking apart pairs, and creating and invoking thunks. To ensure that the values produced by these four kinds of a∞ ’s can be distinguished, we assume that a package is never #f, a function, or a pair whose cdr is a function. To discriminate among these four cases, we define case∞ . (define-syntax case∞ (syntax-rules () (( e (() e0 ) ((fˆ) e1 ) ((â) e2 ) ((a f ) e3 )) (let ((a∞ e)) (cond ((not a∞ ) e0 ) ((procedure? a∞ ) (let ((fˆ a∞ )) e1 )) ((not (and (pair? a∞ ) (procedure? (cdr a∞ )))) (let ((â a∞ )) e2 )) (else (let ((a (car a∞ )) (f (cdr a∞ ))) e3 ))))))) If the first argument to take is #f, then take returns the entire stream of reified values as a list, thereby providing the behavior of run∗ . The and expressions within take detect this #f case. (define take (λ (n f ) (cond ((and n (zero? n)) ’()) (else (case∞ (f ) (() ’()) ((f ) (take n f )) ((a) (cons a ’())) ((a f ) (cons a (take (and n (− n 1)) f )))))))) The interface operator run uses take to convert an f to an even stream (MacQueen et al. 1998). The definition of run places an artificial goal at the tail of g0 g ... This artificial goal invokes reify (Section 3.2.4) on the variable x using the final package a produced by running all the goals in the empty package empty-a (page 15). (define-syntax run (syntax-rules () (( n (x ) g0 g . . . ) (take n (λF () ((fresh (x ) g0 g . . . (reify x )) empty-a)))))) (define-syntax run∗ (syntax-rules () (( (x ) g0 g . . . ) (run #f (x ) g0 g . . . )))) B.1 Goal Constructors The simplest goal constructors are those expanded from goal-construct (page 7); the goals they create return either a singleton stream or an empty stream. To take the conjunction of goals, we define fresh, a goal constructor that first lexically binds variables built by var and then combines successive goals using bindG∗ . (define-syntax fresh (syntax-rules () (( (x . . . ) g0 g . . . ) (λG (a) (inc (let ((x (var ’x)) . . . ) (bindG∗ (g0 a) g . . . ))))))) 17 bindG∗ is short-circuiting: since the empty stream is represented by #f. bindG∗ relies on bind G (Moggi 1991; Wadler 1992), which applies the goal g to each element in the stream a∞ . The resulting a∞ ’s are then merged using mplusG , which combines an a∞ and an f to yield a single a∞ . (define-syntax bindG∗ (syntax-rules () (( e) e) (( e g0 g . . . ) (bindG∗ (bind G e g0 ) g . . . )))) (define bind G (λ (a∞ g) (case∞ a∞ (() (mzeroG )) ((f ) (inc (bind G (f ) g))) ((a) (g a)) ((a f ) (mplusG (g a) (λF () (bind G (f ) g))))))) (define mplusG (λ (a∞ f ) (case∞ a∞ (() (f )) ((fˆ) (inc (mplusG (f ) fˆ))) ((a) (choiceG a f )) ((a fˆ) (choiceG a (λF () (mplusG (f ) fˆ))))))) To take the disjunction of goals we define conde , a goal constructor that combines successive conde lines using mplusG∗ , which in turn relies on mplusG . We use the same implicit package a for each conde line. To avoid unwanted divergence, we treat the conde lines as a single inc stream. (define-syntax conde (syntax-rules () (( (g0 g . . . ) (g1 ĝ . . . ) . . . ) (λG (a) (inc (mplusG∗ (bindG∗ (g0 a) g . . . ) (bindG∗ (g1 a) ĝ . . . ) . . . )))))) (define-syntax mplusG∗ (syntax-rules () (( e) e) (( e0 e . . . ) (mplusG e0 (λF () (mplusG∗ e . . . )))))) B.2 miniKanren Helpers (define (define (define (define (define (define var (λ (dummy) (vector dummy))) var? (λ (x ) (vector? x ))) empty-s ’()) ext-s (λ (x v s) (cons ‘(,x . ,v ) s))) lhs (λ (pr ) (car pr ))) rhs (λ (pr ) (cdr pr ))) (define walk (λ (u s) (cond ((not (var? u)) u) ((assq u s) ⇒ (λ (pr ) (walk (rhs pr ) s))) (else u)))) (define walk∗ (λ (w s) (let ((v (walk w s))) (cond ((var? v ) v ) ((pair? v ) (cons (walk∗ (car v ) s) (walk∗ (cdr v ) s))) (else v ))))) 18 Below is unify (Marriott and Stuckey 1998) but with triangular (Baader and Snyder 2001) instead of idempotent substitutions. In the two-pairs case the substitution does not grow; the equation that had a pair in both sides is dropped and replaced by two new equations: one that equates their respective cars (done now) and one that equates their respective cdrs (done later), so e cannot be empty. Otherwise, there is a recursive call to unify, where either zero or one new association is added to the substitution. Of course, unification can fail. (define unify (λ (e s) (cond ((null? e) s) (else (let loop ((u (caar e)) (v (cdar e)) (e (cdr e))) (let ((u (walk u s)) (v (walk v s))) (cond ((eq? u v ) (unify e s)) ((var? u) √ (and (not (occurs u v s)) (unify e (ext-s u v s)))) ((var? v ) √ (and (not (occurs v u s)) (unify e (ext-s v u s)))) ((and (pair? u) (pair? v )) (loop (car u) (car v ) ‘((,(cdr u) . ,(cdr v )) . ,e))) ((equal? u v ) (unify e s)) (else #f)))))))) √ (define occurs (λ (x v s) (let ((v (walk v s))) (cond ((var? v ) (eq-var? v x )) ((pair? v ) √ √ (or (occurs x (car v ) s) (occurs x (cdr v ) s))) (else #f))))) reify-s is the heart of the reifier. reify-s takes an arbitrary value v , and returns a substitution that maps every distinct variable in v to a unique symbol. The trick to maintaining left-to-right ordering of the subscripts on these symbols is to process v from left to right, as can be seen in the pair? cond line, below. When reify-s encounters a variable, it determines if we already have a mapping for that entity. If not, reify-s extends the substitution with an association between the variable and a new, appropriately subscripted symbol built using reify-n. (define reify-s (λ (v s) (let ((v (walk v s))) (cond ((var? v ) (ext-s v (reify-n (size-s s)) s)) ((pair? v ) (reify-s (cdr v ) (reify-s (car v ) s))) (else s))))) (define reify-n (λ (n) (stringsymbol (string-append " " "." (numberstring n))))) (define size-s (λ (x ) (length x ))) B.3 Impure Control Operators For completeness, we define three additional miniKanren goal constructors: project, which can be used to access the values of variables, and conda and condu , which can be used to prune the search tree of a program. The examples from Thin Ice of The Reasoned Schemer (Friedman et al. 2005) demonstrate how conda and condu can be useful and the pitfalls that await the unsuspecting reader. Also, we have included an additional operator once o , defined in terms of condu , which forces the input goal to succeed at most once. (define-syntax project (syntax-rules () (( (x . . . ) g0 g . . . ) (λG (a : s d c) (let ((x (walk∗ x s)) . . . ) ((fresh () g0 g . . . ) a)))))) (define-syntax conda (syntax-rules () (( (g0 g . . . ) (g1 ĝ . . . ) . . . ) (λG (a) (inc (if a ((g0 a) g . . . ) ((g1 a) ĝ . . . ) . . . )))))) (define-syntax if a (syntax-rules () (( ) (mzeroG )) (( (e g . . . ) b . . . ) (let loop ((a∞ e)) (case∞ a∞ (() (if a b . . . )) ((f ) (inc (loop (f )))) ((a) (bindG∗ a∞ g . . . )) ((a f ) (bindG∗ a∞ g . . . ))))))) (define-syntax condu (syntax-rules () (( (g0 g . . . ) (g1 ĝ . . . ) . . . ) (λG (a) (inc (if u ((g0 a) g . . . ) ((g1 a) ĝ . . . ) . . . )))))) (define-syntax if u (syntax-rules () (( ) (mzeroG )) (( (e g . . . ) b . . . ) (let loop ((a∞ e)) (case∞ a∞ (() (if u b . . . )) ((f ) (inc (loop (f )))) ((a) (bindG∗ a∞ g . . . )) ((a f ) (bindG∗ a g . . . ))))))) (define once o (λ (g) (condu (g)))) 19