123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- ;; import miniKanren
- (use-modules (faster-miniKanren mk-guile))
- ;; lets define a print
- (define (print sth)
- (display
- (simple-format #f "~a\n" sth)))
- ;; and a more special answers printing print-answers
- (define (print-answers answers)
- (define (iter answers count)
- (cond [(null? answers) (print "That's all of them.")]
- [else
- (print (simple-format #f " ~a: ~a" count (car answers)))
- (iter (cdr answers) (+ 1 count))]))
- (print "Answers:")
- (iter answers 1))
- ;; normal member function
- (print (member 5 '(3 4 5 6 7))) ; -> (5 6 7)
- (print (member 5 '(3 4 5 6 5 6 7))) ; -> (5 6 5 6 8)
- (print (member 15 '(3 4 5 6 5 6 7))) ; -> #f
- ;; Lets try to write membero! To start define member in scheme, maybe
- ;; we can again translate from it to miniKanren's membero.
- (define member
- (λ (elem lst)
- (cond
- [(null? lst) #f]
- [(equal? (car lst) elem) lst]
- [else
- (member elem (cdr lst))])))
- (print (member 5 '(3 4 5 6 7))) ; -> (5 6 7)
- (print (member 5 '(3 4 5 6 5 6 7))) ; -> (5 6 5 6 8)
- (print (member 15 '(3 4 5 6 5 6 7))) ; -> #f
- ;; Lets move towards membero.
- (define membero-attempt-1
- (λ (elem lst out)
- (conde
- [(== '() lst) (== out #f)] ; or use nullo, which does the same
- [(fresh (head tail)
- ;; You could use caro, which does the same as the following.
- ;; "deconstructor" cons, sort of like pattern matching
- (== (cons head tail) lst)
- (== head elem) ; if the car of the pair (head of the list) is elem, elem is a member
- (== out l))] ; unify the out variable with the list as a result
- )))
- ;; Since we are unifying the head of the list with elem, we could
- ;; optimize here and put elem into the destructuring cons unification.
- (define membero-attempt-2
- (λ (elem lst out)
- (conde
- [(== '() lst) (== out #f)]
- [(fresh (tail)
- (== (cons elem tail) lst)
- (== out lst))]
- [(fresh (head tail)
- (== (cons head tail) lst)
- (membero-attempt-2 elem tail out))])))
- (print
- (run 1 (q)
- (membero-attempt-2 5 '(3 4 5 6 7) q)))
- (print
- (run 2 (q)
- (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
- (print
- (run* (q)
- (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
- (print-answers
- (run* (q)
- (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
- ;; That query gave multiple answers. But those cannot be all true at
- ;; the same time. An element cannot be member of a list and at the
- ;; same time not be a member of the list. No Schrödinger-ing now
- ;; please! Why is this happening?
- ;; Our conde clauses are overlapping! They are not mutually exclusive!
- ;; That's because conde will try all subexpressions and we do not have
- ;; the implicit negation from the normal scheme cond from the member
- ;; function in out miniKanren version of membero.
- ;; So lets introduce those implicit negations as explicit negations in
- ;; our miniKanren membero code.
- (define membero-attempt-3
- (λ (elem lst out)
- (conde
- [(== '() lst) (== out #f)]
- [(fresh (head tail)
- ;; In the Scheme version of member, there would be an
- ;; implicit test here, which says: If we get a list and
- ;; the condition of the first clause of cond is not true,
- ;; then the list is not empty, so we have at least a
- ;; pair. In miniKanren we would have to write that test
- ;; explicitly as follows ...
- (== (cons head tail) lst)
- ;; ... and then progress as before.
- (== (cons elem tail) lst)
- (== out lst))]
- [(fresh (head tail)
- (== (cons head tail) lst)
- (membero-attempt-3 elem tail out))])))
- ;; However, we already have a destructuring unification with cons in
- ;; there, so we actually already have that test covered. This means we
- ;; do not need to add the test.
- (define membero-attempt-4
- (λ (elem lst out)
- (conde
- [(== '() lst) (== out #f)]
- [(fresh (tail)
- ;; This fine.
- (== (cons elem tail) lst)
- (== out lst))]
- [(fresh (head tail)
- ;; Here however, we are not expressing, that the head of
- ;; the list is not equal to the element we are looking
- ;; for. That is a problem. We would like to write
- ;; something like (=/= head elem) and there actually _is_
- ;; =/= in miniKanren. Let's use it.
- (=/= head elem)
- (== (cons head tail) lst)
- (membero-attempt-4 elem tail out))])))
- (print-answers
- (run* (q)
- (membero-attempt-4 5 '(3 4 5 6 5 6 7) q)))
- (print-answers
- (run* (q)
- (membero-attempt-4 100 '(3 4 5 6 5 6 7) q)))
- ;; We get back #f for trying to find members that are not in the
- ;; list. The problem with that is, that #f will be an answer for the
- ;; query and it could mean something like "The element you are looking
- ;; for is in the sublist #f.". In this case #f is not a sublist and it
- ;; would still be clear, but in other cases such an answer could be
- ;; confusing and difficult to interpret correctly. To fix this kind of
- ;; behavior, we could simply remove the case, where we unify the
- ;; output variable out with #f. We could remove the base case.
- (define membero-attempt-5
- (λ (elem lst out)
- (conde
- [(fresh (tail)
- (== (cons elem tail) lst)
- (== out lst))]
- [(fresh (head tail)
- (=/= head elem)
- (== (cons head tail) lst)
- (membero-attempt-5 elem tail out))])))
- (print-answers
- (run* (q)
- (membero-attempt-5 5 '(3 4 5 6 5 6 7) q)))
- (print-answers
- (run* (q)
- (membero-attempt-5 100 '(3 4 5 6 5 6 7) q)))
- ;; This is what we want for now, so now really define membero.
- (define membero membero-attempt-5)
- (print-answers
- (run* (q)
- (membero 5 '(3 4 5 6 5 6 7) q)))
- (print-answers
- (run* (q)
- (membero 100 '(3 4 5 6 5 6 7) q)))
|