123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- ;; import miniKanren
- (use-modules (faster-miniKanren mk-guile))
- ;; normal append function
- (append '(1 2 3) '(4 5 6)) ; -> '(1 2 3 4 5 6)
- (append '() '(4 5 6)) ; -> '(4 5 6)
- ;; normal definition
- (define append
- (λ (l1 l2)
- (cond
- [(null? l1) l2]
- [else
- (cons (car l1)
- (append (cdr l1) l2))])))
- (display
- (simple-format #f
- "~a\n"
- (append '(1 2) '(3 4))))
- ;; lets define a print
- (define (print sth)
- (display
- (simple-format #f "~a\n" sth)))
- (print (run 1 (q) (== q 6) (== q 5)))
- (print (run 1 (q) (== q 6)))
- (print
- (run* (q x y z)
- (== x 5)
- (== y x)
- (== z y)
- (== q `(,x ,y ,z))))
- ;; Now lets try to turn append into a relation instead of a function
- ;; using miniKanren!
- (define appendo-attempt-1
- ;; `out` will be unified with the value of the resulting list.
- ;; Instead of returning values we are associating values with `out`.
- (λ (l1 l2 out)
- (conde
- [(== l1 '())
- ;; Here we need to associate `out` with the value of `l2`,
- ;; because `l1` has no elements to append to.
- (== l2 out)])))
- (print
- (run* (q)
- (appendo-attempt-1 '() '(4 5) q)))
- (print
- (run* (q)
- (appendo-attempt-1 '(1 2 3) '(4 5) q)))
- ;; We got the empty list, because appendo could not unify, because we
- ;; only have the conde clause where l1 is empty.
- ;; So let us define the other clauses.
- (define appendo-attempt-2
- (λ (l1 l2 out)
- (conde
- [(== l1 '()) (== l2 out)]
- ;; For the else case we could write a clause that always
- ;; succeeds, for example (== 5 5). Or we could define an `elso`
- ;; to put in like that. Instead we have a goal that always
- ;; succeeds called `succeed`. However, it is not required, as we
- ;; could simply put the relations we want to succeed there
- ;; instead. `conde` stands for `cond` + `every`, so it is going
- ;; to try all case anyway. So we do not even need `succeed` here.
- ;; It can be difficult to express that something is not a value,
- ;; because we have only == and not `not` or `!=` or things like
- ;; that. miniKanren is based on a search, which tries to match
- ;; things, so it is difficult to express that is should search
- ;; for not matching something. So we need to be clever and find
- ;; a positive expression to express the opposite of matching a
- ;; value. In this case we know that we get a list as input, so
- ;; the opposite of being the empty list is being at least a pair,
- ;; assuming we really got a list.
- [(fresh (head tail result)
- (== (cons head tail) l1)
- ;; Associate cdr of the list with `tail`, explanation
- ;; below. Actually this is redundant because `tail` is
- ;; already unified to the cdr of the list by the
- ;; unification (== (cons head tail) l1). So lets comment
- ;; it out.
- #;(cdro l1 tail)
- ;; The equivalent would be:
- ;; (cdro l1 tail)
- ;; (caro l1 head)
- #; (appendo-attempt-2
- ;; We cannot take the cdr of a logic variable. So we cannot use
- ;; cdr like in the normal append definition. There is `cdro` in
- ;; miniKanren. However, `cdro` does not work like the normal
- ;; cdr. Instead it associates the cdr of a list with another
- ;; value (specifying the search space more).
- ;; `cdro` does not return a value, but we need a value for the
- ;; recursive call to appendo. So we need to pull out cdro and
- ;; use the variable it associates with the cdr of the list as
- ;; an argument. That's why we have it above this clause!
- tail
- l2
- ???)
- ;; In the normal version of append we return the value of
- ;; cons. In the relational world we need to unify out
- ;; with the value constructed by cons.
- #;(== (cons head
- (appendo-attempt-2 tail l2 ???))
- out)
- ;; However, we cannot nest the call to appendo in the
- ;; cons. That's just miniKanren for you. So pull it out
- ;; again!
- (appendo-attempt-2 tail l2 result) ; unification with result
- ;; and then use it in the cons to associate with out,
- ;; promoting the result of appendo to the
- ;; relationship-scoped `out` of appendo.
- (== (cons head result) out))])))
- ;; In short:
- (define appendo-attempt-2-short
- (λ (l1 l2 out)
- (conde
- [(== l1 '()) (== l2 out)]
- [(fresh (head tail result)
- (== (cons head tail) l1)
- (appendo-attempt-2 tail l2 result)
- (== (cons head result) out))])))
- ;; However, if we ask this appendo for more than the possible number
- ;; of solutions, it will become not terminate. This is because the
- ;; recursive call to appendo is not in the last position. Because it
- ;; is not in the last position, what happens is, that ... ??? (It's
- ;; complicated!) So we can see, that the order of calls inside a
- ;; fresh does in fact matter, which has to do with how miniKanren is
- ;; implemented.
- (define appendo
- (λ (l1 l2 out)
- (conde
- [(== l1 '()) (== l2 out)]
- [(fresh (head tail result)
- (== (cons head tail) l1)
- (== (cons head result) out)
- (appendo tail l2 result))])))
- ;; Generally speaking, we can swap the following:
- ;; 1. == clauses
- ;; 2. conde clauses
- ;; but not the following:
- ;; 1. recursive calls (This is an open research problem for the authors of miniKanren!)
- (print
- (run* (out)
- (appendo '() '(4 5) out)))
- (print
- (run* (out)
- (appendo '(1 2 3) '(4 5) out)))
- (print
- (run* (out)
- (fresh (a)
- (== a '(-1 -2))
- (appendo `(,a) '(4 5) out))))
- ;; We can now do persistency checks!
- (print
- (run* (q) (appendo '(1 2 3) '(4 5) '(1 2 3 4 5))))
- (print
- (run* (q) (appendo '(1 2 3) '(4 5) '(1 2 3 4 5 6))))
- ;; We can ask questions!
- (print
- (run 1 (q) (appendo '(1 2 3) q '(1 2 3 4 5))))
- (print
- (run 1 (q) (appendo q '(4 5) '(1 2 3 4 5))))
- (print
- (run 6 (a b) (appendo a b '(1 2 3 4 5))))
- (print
- (run 2 (a b c) (appendo a b c)))
- ;; What we are doing here is basically some type of theorem proving
- ;; about infinitely many values, which would not be possible with the
- ;; normal append.
- ;; We can mathematically prove, that there are no more ways to build a certain list using run*.
- (print
- (run* (a b)
- (appendo a b '(1 2 3 4 5))))
|