1

I began studying some minikanren and made up this example for counting 1-bits in a list.

(define (count xs n)
  (conde
   [(== xs '()) (== n 0)]
   [(fresh (a b)
           (== xs (cons a b))
           (conde
            [(== a 0) (count b n)]
            [(== a 1) (count b (- n 1))]))]))

Now the following works fine

> (run* (q) (fresh (a b c) (== q (list a b c)) (count q 1)))
'((0 0 1) (1 0 0) (0 1 0))

but if I change the order of the goals the program does not terminate

> (run* (q) (fresh (a b c) (count q 1) (== q (list a b c))))
. . user break

This is already something that I don't completely understand, does the order of goals matter? In which ways? I think I understood that even the subsequent goals can pose sufficient constraints so as to terminate the search, but of course I must have missed something.

Exploring more of this problem I saw that

> (run 3 (q) (fresh (a b c) (count q 1) (== q (list a b c))))
'((0 0 1) (0 1 0) (1 0 0))
> (run 4 (q) (fresh (a b c) (count q 1) (== q (list a b c))))
. . user break

so I know that it tries to look for more answers than existing. My suspect now is that when the subgoal

(== xs (cons a b))

splits the list there aren't sufficient constraints on b to make the search finite, which sounds reasonable given that nothing talks about the tail of the list besides the external (== q (list a b c)). It seems even more reasonable to expect a non termination as nothing says what c is in (== q (list a b c)), it may be another list (is it true?), but so how does the swapped version of the program terminate?

I therefore tried

(define (bit x)
  (conde [(== x 0)] [(== x 1)]))

and

> (run 4 (q) (fresh (a b c) (bit a) (bit b) (bit c) (count q 1) (== q (list a b c))))
. . user break

but it still does not terminate, so maybe the problem is not that which I thought.

Overall there must be something that I'm missing in my understanding, can anyone clarify all this and point out what would be a correct way of doing this thing?

user9137
  • 165
  • 4

1 Answers1

0
> (run* (q) (fresh (a b c) (== q (list a b c)) (count q 1)))
'((0 0 1) (1 0 0) (0 1 0))

This query terminated because the 1st conjunct (== q (list a b c)) bound the size of q ,i.e. length of (list a b c) is 3. So the 2nd conjunct (count q 1) can only succeed finitely many times. Thus it terminates.

(run* (q) (fresh (a b c) (count q 1) (== q (list a b c))))
. . user break

This query diverged because the 1st conjunct (count q 1) keeps associating q with larger and larger lists without bound. Note that the 2nd conjunct (== q (list a b c)) can produce three answers, but after that, it will never succeed. Thus whenever it fails, it backtracks into (count q 1), which succeeds again, and fails again, backtracks again, ... and repeats forever (q will be larger and larger).

You asked that

This is already something that I don't completely understand, does the order of goals matter?

The answer is yes.

As William E. Byrd said in his dissertation:

miniKanren’s conjunction operator (exist) is commutative, but only if an answer exists. If no answer exists, then reordering goals within an exist may result in divergence rather than failure.

We say that conjunction is commutative, modulo divergence versus failure.

chansey
  • 1,266
  • 9
  • 20