core.logic stackoverflow when using sets

放肆的年华 提交于 2021-01-03 02:08:39

问题


It seems that clojure.core.logic has a problem walking sets. The minimal failing example:

(run* [q] (== q #{}))

produces

java.lang.StackOverflowError at clojure.core.logic.Substitutions.walk(logic.clj:344) at clojure.core.logic$walk_STAR_$fn_2633.invoke(logic.clj:216) at clojure.core.logic$eval2838$fn_2839.invoke(logic.clj:956) at clojure.core.logic.protocols$eval1389$fn_1390$G_1380__1397.invoke(protocols.clj:55) at clojure.core.logic$walk_STAR_.invoke(logic.clj:214) at clojure.core.logic$walk_STAR_$fn_2633.invoke(logic.clj:218) at clojure.core.logic$eval2838$fn_2839.invoke(logic.clj:956) at clojure.core.logic.protocols$eval1389$fn_1390$G_1380__1397.invoke(protocols.clj:55) at clojure.core.logic$walk_STAR_.invoke(logic.clj:214) at clojure.core.logic$walk_STAR_$fn_2633.invoke(logic.clj:218) at clojure.core.logic$eval2838$fn_2839.invoke(logic.clj:956) at clojure.core.logic.protocols$eval1389$fn_1390$G_1380__1397.invoke(protocols.clj:55) at clojure.core.logic$walk_STAR_.invoke(logic.clj:214) at clojure.core.logic$walk_STAR_$fn_2633.invoke(logic.clj:218) at clojure.core.logic$eval2838$fn_2839.invoke(logic.clj:956) at clojure.core.logic.protocols$eval1389$fn_1390$G_1380__1397.invoke(protocols.clj:55)

Why does this generate a Stackoverflow? unifying with empty vectors/lists/maps/other types works as expected.


回答1:


You have an answer from the originating author of core.logic that sets aren't supported, but I think you could have phrased your question to be more leading and may have gotten a more interesting response as to why sets aren't supported (yet) or what it might take to support them. As to the why, I suspect they are really not needed because distincto and permuteo provide goals that can be used to test set properties. As to what it might take to support them in unification, follow along below for a rough, ugly, incomplete and inefficient first look.

The stack overflow occurs because sets are collections and collections are recursed into while walking. But since sets are not supported there is no walk implementation for sets and the default for objects is to return itself. The net result is that from the point of view of the walk sets contain themselves and the stack is blown trying to recurse to the bottom.

Join me on the REPL while looking at the source and let's hack together something.

(use 'clojure.core.logic)
(use 'clojure.core.logic.protocols)

Let's tell core.logic to walk sets by using the existing implementation for sequences.

(extend-protocol IWalkTerm 
  clojure.lang.IPersistentSet 
  (walk-term [v f] (with-meta (set (walk-term (seq v) f)) (meta v))))

(run* [q] (== q []))
;=> ([])
(run* [q] (== q #{}))
;=> (#{})

Good so far...

(run* [q] (== q [1 2 3]))
;=> ([1 2 3])
(run* [q] (== q #{1 2 3}))
;=> (#{1 2 3})

Consistent, but not terribly useful

(run* [q] (== [1 q 3] [1 2 3]))
;=> (2)
(run* [q] (== #{1 q 3} #{1 2 3}))
;=> ()
(run* [q] (== #{1 3 q} #{1 2 3}))
;=> ()

Now we have a problem. Both of the last two should return (2) since sets have no order, but both return no results. We need to tell core.logic how to unify sets as well. Let's be lazy and try to use the existing permuteo to convey the lack of order.

(extend-protocol IUnifyTerms 
  clojure.lang.IPersistentSet 
  (unify-terms [u v s] (bind s (permuteo (seq u) (seq v)))))

(run* [q] (== #{1 q 3} #{1 2 3}))
;=> (2)
(run* [q] (== #{3 1 q} #{1 2 3})) 
;=> (2)

Excellent!

(run* [q] (fresh [a1 a2 a3] (== #{a1 a2 a3} #{1 2 3}) (== q [a1 a2 a3])))
;=> ([1 2 3] [2 1 3] [1 3 2] [3 1 2] [2 3 1] [3 2 1])

Very cool.

(run* [q] (== #{1 2 [3 q]} #{1 2 [3 4]}))
;=> (4)

Nice...but

(run* [q] (== #{1 2 #{3 q}} #{1 2 #{3 4}}))
;=> IllegalArgumentException No implementation of method: :walk of protocol:  #'clojure.core.logic.protocols/ISubstitutions found for class: clojure.core.logic$permuteo$fn...

So we were a bit too sloppy using permuteo, let's try to kludge it with clojure.math.combinatorics instead

(use 'clojure.math.combinatorics)

(extend-protocol IUnifyTerms 
  clojure.lang.IPersistentSet 
  (unify-terms [u v s]
    (when (set? v)
      (let [u (seq u)
            v (seq v)]
        (reduce #(mplus % (-inc %2)) 
                (for [p (permutations u)] (unify s p v))))))) 

And now...

(run* [q] (== #{1 2 #{3 q}} #{1 2 #{3 4}}))
;=> 4

(run* [q] (== #{ #{ #{q} :bar} :baz}  #{:baz #{:bar #{:foo} } }))
;=> (:foo)

Looks quite promising again.




回答2:


Sets are not supported in core.logic.



来源:https://stackoverflow.com/questions/18017924/core-logic-stackoverflow-when-using-sets

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!