Consider this blog post where the author implements palindrome relation using reverso:
(defn reverso [l r]
(conde
[(== l ()) (== r ())]
[(fresh [la ld ldr]
(conso la ld l)
(appendo ldr (list la) r)
(reverso ld ldr))]))
When I run (run* [q] (reverso q [1 2 3])), the output is ([3 2 1]). However, when I run (run* [q] (reverso [1 2 3] q)), the program does not terminate. I am able to get the correct result by explicitly requesting one result via run 1.
When experimenting, I was able to implement the same function myself using defne:
(defne conjo2 [l a la]
([[] _ [a]])
([[c . bs] _ [c . cs]] (conjo2 bs a cs)))
(defne reverso [l r]
([[] []])
([[a . ls] _]
(fresh [rs]
(conjo2 rs a r)
(reverso ls rs))))
but the same issue is present. However, if I change my custom conjo2 to core.logic/conjo, the position of the variable during non-termination changes.
- Why does the program not terminate for the general query with specific locations of the query variable?
- Why specifying
run 1succeeds, but as soon asrun 2the program does not terminate?
The problem is that when you give
appendoa fresh variable on the left and a fresh variable for the result, it can produce arbitrarily many answers, none of which will lead to a solution for your overallreversoquestion. Witness the definition ofappendo:It terminates if the
xargument is empty (succeeding withyas the result); otherwise, it assumes that bothxand the resultzare non-empty lists and calls itself recursively. This terminates if eitherxorzcan be proven to have a finite length, or if an index they share has a different value in the two lists. You never impose either of those constraints, so it continues searching forever.Let's step through the execution of a simpler query,
(run 2 [q] (reverso [1] q)).landrare both empty(conso la ld [1])(== la 1)and(== ld []).(appendo ldr [1] r).appendo, binding(== ldr [])and(== r [1]).(reverso [] []), and check whether both arguments are empty.(== q [1])(remember that at the outer level(== q r), so a solution forris a solution forq).(== l [])at this point, so theconsofails.(appendo ldr [1] r). Rejecting this puts us in the second clause,(appendo d [1] r1), binding(== ldr [a . d])and(== r1 [a . r]).You can see this will never lead anywhere: the lists we're looking through are longer than the list we're trying to reverse. But before it can fail, it will make another recursive call to
reverso, which will make a call toappendo, which will lengthen the lists again and make a recursive call toreverso, and so on. This is a simlar problem to that described in Goal ordering in Clojure's `core.logic`.You can fix this by first informing the engine that you expect the two lists to be the same length. Then attempts by
appendoto lengthen its input and output beyond the limit will fail. First, definesame-lengtho:Then insert a call to
same-lengthobefore you start the main search1:Defined as such, a
run*query produces the correct results regardless of parameter order:And just to show that we haven't accidentally done something one-directional with
same-lengtho, observe that with totally fresh variables on both sides it still generates all possible lists and their reverses:1 You don't technically need both
(== l ())and(== r ())anymore: either one will suffice since you pre-guaranteed that the lists are the same length. But to me it seems semantically nicer to leave in the redundant check.