membero.scm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. ;; import miniKanren
  2. (use-modules (faster-miniKanren mk-guile))
  3. ;; lets define a print
  4. (define (print sth)
  5. (display
  6. (simple-format #f "~a\n" sth)))
  7. ;; and a more special answers printing print-answers
  8. (define (print-answers answers)
  9. (define (iter answers count)
  10. (cond [(null? answers) (print "That's all of them.")]
  11. [else
  12. (print (simple-format #f " ~a: ~a" count (car answers)))
  13. (iter (cdr answers) (+ 1 count))]))
  14. (print "Answers:")
  15. (iter answers 1))
  16. ;; normal member function
  17. (print (member 5 '(3 4 5 6 7))) ; -> (5 6 7)
  18. (print (member 5 '(3 4 5 6 5 6 7))) ; -> (5 6 5 6 8)
  19. (print (member 15 '(3 4 5 6 5 6 7))) ; -> #f
  20. ;; Lets try to write membero! To start define member in scheme, maybe
  21. ;; we can again translate from it to miniKanren's membero.
  22. (define member
  23. (λ (elem lst)
  24. (cond
  25. [(null? lst) #f]
  26. [(equal? (car lst) elem) lst]
  27. [else
  28. (member elem (cdr lst))])))
  29. (print (member 5 '(3 4 5 6 7))) ; -> (5 6 7)
  30. (print (member 5 '(3 4 5 6 5 6 7))) ; -> (5 6 5 6 8)
  31. (print (member 15 '(3 4 5 6 5 6 7))) ; -> #f
  32. ;; Lets move towards membero.
  33. (define membero-attempt-1
  34. (λ (elem lst out)
  35. (conde
  36. [(== '() lst) (== out #f)] ; or use nullo, which does the same
  37. [(fresh (head tail)
  38. ;; You could use caro, which does the same as the following.
  39. ;; "deconstructor" cons, sort of like pattern matching
  40. (== (cons head tail) lst)
  41. (== head elem) ; if the car of the pair (head of the list) is elem, elem is a member
  42. (== out l))] ; unify the out variable with the list as a result
  43. )))
  44. ;; Since we are unifying the head of the list with elem, we could
  45. ;; optimize here and put elem into the destructuring cons unification.
  46. (define membero-attempt-2
  47. (λ (elem lst out)
  48. (conde
  49. [(== '() lst) (== out #f)]
  50. [(fresh (tail)
  51. (== (cons elem tail) lst)
  52. (== out lst))]
  53. [(fresh (head tail)
  54. (== (cons head tail) lst)
  55. (membero-attempt-2 elem tail out))])))
  56. (print
  57. (run 1 (q)
  58. (membero-attempt-2 5 '(3 4 5 6 7) q)))
  59. (print
  60. (run 2 (q)
  61. (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
  62. (print
  63. (run* (q)
  64. (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
  65. (print-answers
  66. (run* (q)
  67. (membero-attempt-2 5 '(3 4 5 6 5 6 7) q)))
  68. ;; That query gave multiple answers. But those cannot be all true at
  69. ;; the same time. An element cannot be member of a list and at the
  70. ;; same time not be a member of the list. No Schrödinger-ing now
  71. ;; please! Why is this happening?
  72. ;; Our conde clauses are overlapping! They are not mutually exclusive!
  73. ;; That's because conde will try all subexpressions and we do not have
  74. ;; the implicit negation from the normal scheme cond from the member
  75. ;; function in out miniKanren version of membero.
  76. ;; So lets introduce those implicit negations as explicit negations in
  77. ;; our miniKanren membero code.
  78. (define membero-attempt-3
  79. (λ (elem lst out)
  80. (conde
  81. [(== '() lst) (== out #f)]
  82. [(fresh (head tail)
  83. ;; In the Scheme version of member, there would be an
  84. ;; implicit test here, which says: If we get a list and
  85. ;; the condition of the first clause of cond is not true,
  86. ;; then the list is not empty, so we have at least a
  87. ;; pair. In miniKanren we would have to write that test
  88. ;; explicitly as follows ...
  89. (== (cons head tail) lst)
  90. ;; ... and then progress as before.
  91. (== (cons elem tail) lst)
  92. (== out lst))]
  93. [(fresh (head tail)
  94. (== (cons head tail) lst)
  95. (membero-attempt-3 elem tail out))])))
  96. ;; However, we already have a destructuring unification with cons in
  97. ;; there, so we actually already have that test covered. This means we
  98. ;; do not need to add the test.
  99. (define membero-attempt-4
  100. (λ (elem lst out)
  101. (conde
  102. [(== '() lst) (== out #f)]
  103. [(fresh (tail)
  104. ;; This fine.
  105. (== (cons elem tail) lst)
  106. (== out lst))]
  107. [(fresh (head tail)
  108. ;; Here however, we are not expressing, that the head of
  109. ;; the list is not equal to the element we are looking
  110. ;; for. That is a problem. We would like to write
  111. ;; something like (=/= head elem) and there actually _is_
  112. ;; =/= in miniKanren. Let's use it.
  113. (=/= head elem)
  114. (== (cons head tail) lst)
  115. (membero-attempt-4 elem tail out))])))
  116. (print-answers
  117. (run* (q)
  118. (membero-attempt-4 5 '(3 4 5 6 5 6 7) q)))
  119. (print-answers
  120. (run* (q)
  121. (membero-attempt-4 100 '(3 4 5 6 5 6 7) q)))
  122. ;; We get back #f for trying to find members that are not in the
  123. ;; list. The problem with that is, that #f will be an answer for the
  124. ;; query and it could mean something like "The element you are looking
  125. ;; for is in the sublist #f.". In this case #f is not a sublist and it
  126. ;; would still be clear, but in other cases such an answer could be
  127. ;; confusing and difficult to interpret correctly. To fix this kind of
  128. ;; behavior, we could simply remove the case, where we unify the
  129. ;; output variable out with #f. We could remove the base case.
  130. (define membero-attempt-5
  131. (λ (elem lst out)
  132. (conde
  133. [(fresh (tail)
  134. (== (cons elem tail) lst)
  135. (== out lst))]
  136. [(fresh (head tail)
  137. (=/= head elem)
  138. (== (cons head tail) lst)
  139. (membero-attempt-5 elem tail out))])))
  140. (print-answers
  141. (run* (q)
  142. (membero-attempt-5 5 '(3 4 5 6 5 6 7) q)))
  143. (print-answers
  144. (run* (q)
  145. (membero-attempt-5 100 '(3 4 5 6 5 6 7) q)))
  146. ;; This is what we want for now, so now really define membero.
  147. (define membero membero-attempt-5)
  148. (print-answers
  149. (run* (q)
  150. (membero 5 '(3 4 5 6 5 6 7) q)))
  151. (print-answers
  152. (run* (q)
  153. (membero 100 '(3 4 5 6 5 6 7) q)))