appendo.scm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. ;; import miniKanren
  2. (use-modules (faster-miniKanren mk-guile))
  3. ;; normal append function
  4. (append '(1 2 3) '(4 5 6)) ; -> '(1 2 3 4 5 6)
  5. (append '() '(4 5 6)) ; -> '(4 5 6)
  6. ;; normal definition
  7. (define append
  8. (λ (l1 l2)
  9. (cond
  10. [(null? l1) l2]
  11. [else
  12. (cons (car l1)
  13. (append (cdr l1) l2))])))
  14. (display
  15. (simple-format #f
  16. "~a\n"
  17. (append '(1 2) '(3 4))))
  18. ;; lets define a print
  19. (define (print sth)
  20. (display
  21. (simple-format #f "~a\n" sth)))
  22. (print (run 1 (q) (== q 6) (== q 5)))
  23. (print (run 1 (q) (== q 6)))
  24. (print
  25. (run* (q x y z)
  26. (== x 5)
  27. (== y x)
  28. (== z y)
  29. (== q `(,x ,y ,z))))
  30. ;; Now lets try to turn append into a relation instead of a function
  31. ;; using miniKanren!
  32. (define appendo-attempt-1
  33. ;; `out` will be unified with the value of the resulting list.
  34. ;; Instead of returning values we are associating values with `out`.
  35. (λ (l1 l2 out)
  36. (conde
  37. [(== l1 '())
  38. ;; Here we need to associate `out` with the value of `l2`,
  39. ;; because `l1` has no elements to append to.
  40. (== l2 out)])))
  41. (print
  42. (run* (q)
  43. (appendo-attempt-1 '() '(4 5) q)))
  44. (print
  45. (run* (q)
  46. (appendo-attempt-1 '(1 2 3) '(4 5) q)))
  47. ;; We got the empty list, because appendo could not unify, because we
  48. ;; only have the conde clause where l1 is empty.
  49. ;; So let us define the other clauses.
  50. (define appendo-attempt-2
  51. (λ (l1 l2 out)
  52. (conde
  53. [(== l1 '()) (== l2 out)]
  54. ;; For the else case we could write a clause that always
  55. ;; succeeds, for example (== 5 5). Or we could define an `elso`
  56. ;; to put in like that. Instead we have a goal that always
  57. ;; succeeds called `succeed`. However, it is not required, as we
  58. ;; could simply put the relations we want to succeed there
  59. ;; instead. `conde` stands for `cond` + `every`, so it is going
  60. ;; to try all case anyway. So we do not even need `succeed` here.
  61. ;; It can be difficult to express that something is not a value,
  62. ;; because we have only == and not `not` or `!=` or things like
  63. ;; that. miniKanren is based on a search, which tries to match
  64. ;; things, so it is difficult to express that is should search
  65. ;; for not matching something. So we need to be clever and find
  66. ;; a positive expression to express the opposite of matching a
  67. ;; value. In this case we know that we get a list as input, so
  68. ;; the opposite of being the empty list is being at least a pair,
  69. ;; assuming we really got a list.
  70. [(fresh (head tail result)
  71. (== (cons head tail) l1)
  72. ;; Associate cdr of the list with `tail`, explanation
  73. ;; below. Actually this is redundant because `tail` is
  74. ;; already unified to the cdr of the list by the
  75. ;; unification (== (cons head tail) l1). So lets comment
  76. ;; it out.
  77. #;(cdro l1 tail)
  78. ;; The equivalent would be:
  79. ;; (cdro l1 tail)
  80. ;; (caro l1 head)
  81. #; (appendo-attempt-2
  82. ;; We cannot take the cdr of a logic variable. So we cannot use
  83. ;; cdr like in the normal append definition. There is `cdro` in
  84. ;; miniKanren. However, `cdro` does not work like the normal
  85. ;; cdr. Instead it associates the cdr of a list with another
  86. ;; value (specifying the search space more).
  87. ;; `cdro` does not return a value, but we need a value for the
  88. ;; recursive call to appendo. So we need to pull out cdro and
  89. ;; use the variable it associates with the cdr of the list as
  90. ;; an argument. That's why we have it above this clause!
  91. tail
  92. l2
  93. ???)
  94. ;; In the normal version of append we return the value of
  95. ;; cons. In the relational world we need to unify out
  96. ;; with the value constructed by cons.
  97. #;(== (cons head
  98. (appendo-attempt-2 tail l2 ???))
  99. out)
  100. ;; However, we cannot nest the call to appendo in the
  101. ;; cons. That's just miniKanren for you. So pull it out
  102. ;; again!
  103. (appendo-attempt-2 tail l2 result) ; unification with result
  104. ;; and then use it in the cons to associate with out,
  105. ;; promoting the result of appendo to the
  106. ;; relationship-scoped `out` of appendo.
  107. (== (cons head result) out))])))
  108. ;; In short:
  109. (define appendo-attempt-2-short
  110. (λ (l1 l2 out)
  111. (conde
  112. [(== l1 '()) (== l2 out)]
  113. [(fresh (head tail result)
  114. (== (cons head tail) l1)
  115. (appendo-attempt-2 tail l2 result)
  116. (== (cons head result) out))])))
  117. ;; However, if we ask this appendo for more than the possible number
  118. ;; of solutions, it will become not terminate. This is because the
  119. ;; recursive call to appendo is not in the last position. Because it
  120. ;; is not in the last position, what happens is, that ... ??? (It's
  121. ;; complicated!) So we can see, that the order of calls inside a
  122. ;; fresh does in fact matter, which has to do with how miniKanren is
  123. ;; implemented.
  124. (define appendo
  125. (λ (l1 l2 out)
  126. (conde
  127. [(== l1 '()) (== l2 out)]
  128. [(fresh (head tail result)
  129. (== (cons head tail) l1)
  130. (== (cons head result) out)
  131. (appendo tail l2 result))])))
  132. ;; Generally speaking, we can swap the following:
  133. ;; 1. == clauses
  134. ;; 2. conde clauses
  135. ;; but not the following:
  136. ;; 1. recursive calls (This is an open research problem for the authors of miniKanren!)
  137. (print
  138. (run* (out)
  139. (appendo '() '(4 5) out)))
  140. (print
  141. (run* (out)
  142. (appendo '(1 2 3) '(4 5) out)))
  143. (print
  144. (run* (out)
  145. (fresh (a)
  146. (== a '(-1 -2))
  147. (appendo `(,a) '(4 5) out))))
  148. ;; We can now do persistency checks!
  149. (print
  150. (run* (q) (appendo '(1 2 3) '(4 5) '(1 2 3 4 5))))
  151. (print
  152. (run* (q) (appendo '(1 2 3) '(4 5) '(1 2 3 4 5 6))))
  153. ;; We can ask questions!
  154. (print
  155. (run 1 (q) (appendo '(1 2 3) q '(1 2 3 4 5))))
  156. (print
  157. (run 1 (q) (appendo q '(4 5) '(1 2 3 4 5))))
  158. (print
  159. (run 6 (a b) (appendo a b '(1 2 3 4 5))))
  160. (print
  161. (run 2 (a b c) (appendo a b c)))
  162. ;; What we are doing here is basically some type of theorem proving
  163. ;; about infinitely many values, which would not be possible with the
  164. ;; normal append.
  165. ;; We can mathematically prove, that there are no more ways to build a certain list using run*.
  166. (print
  167. (run* (a b)
  168. (appendo a b '(1 2 3 4 5))))