core-logic

tschady 2021-11-08T18:57:53.008300Z

My first core.logic program. i’m trying to have lvars be values in a map of known keys. This code doesn’t seem to run the membero functions. Any help? I only have m at runtime, so I can’t just hardcode in 2 logic vars, m could have 100 keys for instance. I think the problem is in the for .. Also, any other advice very welcome.

(def m {:a [[1 2 3]
            [2 100 200]]
        :b [[1 2 3]
            [2 3 4]]})

(run* [q]
  (let [result-map (zipmap (keys m) (repeatedly lvar))]
    (for [k (keys m)
          vs (get m k)]
      (membero (get result-map k) vs))
    (fd/distinct (vals result-map))
    (== q result-map)))
; => ({:a _0, :b _1})

paulocuneo 2021-11-08T19:15:39.008500Z

minikanren is an inmutable lang/framework. goals will not be evaluated as logic relations outside run*, this means the goals inside let expression have no effect, only the last goals of let is passed into run*, this means only the last goal from the let body is evaluated. additionally, functional expression will not be evaluated as relations, this means is not posible to use a (functional) for expression, you will need to re-write the for in a relational way ( Disclaimer: I'm not an expert, nor a english native speaker 😄 )

tschady 2021-11-08T19:25:17.009Z

very helpful, thanks!

paulocuneo 2021-11-08T19:45:08.009200Z

https://github.com/clojure/core.logic/blob/master/src/main/clojure/clojure/core/logic.clj#L1281 and* will come in handy to transform the seq returned by the for expresion into a goal/relation https://github.com/clojure/core.logic/blob/master/src/main/clojure/clojure/core/logic.clj#L1276 all can combine the goals inside let body into a goal something like

(def m {:a [[1 2 3]
            [2 100 200]]
        :b [[1 2 3]
            [2 3 4]]})

(run* [q]
  (let [result-map (zipmap (keys m) (repeatedly lvar))]
    (all (and* (for [k  (keys m)
                     vs (get m k)]
                 (membero (get result-map k) vs)))
         (fd/distinct (vals result-map))
         (== q result-map))))
must say this is quite hackish, there should be a relational way to express what you want

tschady 2021-11-08T19:55:52.009700Z

just did my own before comparing, works:

tschady 2021-11-08T19:55:56.009900Z

(def m {:a [[1 2 3]
            [2 100 200]]
        :b [[1 2 3]
            [2 3 4]]})

(defn member-all [var possibles]
  (and* (map #(membero var %) possibles)))

(let [vars (repeatedly (count (keys m)) lvar)]
  (apply zipmap
         (keys m)
         (run 1 [q]
           (and* (map #(member-all % %2) vars (vals m)))
           (fd/distinct vars)
           (== q vars))))

tschady 2021-11-08T19:56:59.010100Z

tho this depends on keys/vals of map always returning in same order

tschady 2021-11-08T19:59:49.010300Z

thanks for the help, changed my thinking. i’ll keep at it.

tschady 2021-11-08T20:07:55.010500Z

getting better, moved out the let and got rid of the for:

(def m {:a [[1 2 3]
            [2 100 200]]
        :b [[1 2 3]
            [2 3 4]]})

(defn member-all [var possibles]
  (and* (map #(membero var %) possibles)))

(let [result-map (zipmap (keys m) (repeatedly lvar))]
  (run* [q]
    (and* (map (fn [[k vs]] (member-all (get result-map k) vs)) m))
    (distincto (vals result-map))
    (== q result-map)))

tschady 2021-11-08T20:19:42.011Z

this is super slow on large input btw

2021-11-08T20:25:20.011200Z

membero is not fast

2021-11-08T20:27:51.011400Z

It can be hard to give big o measurements of logic programs, because of how abstract they can be, but membero is basically o(n) of the collection

2021-11-08T20:28:05.011600Z

And you are doing it over and over again

paulocuneo 2021-11-08T20:29:31.011800Z

maybe the nail doesnt fit the hammer, dont know a better way to phrase it are you planning to run the query backwards?

tschady 2021-11-08T20:30:09.012100Z

stupid nail! swing harder.

😆 1
tschady 2021-11-08T20:30:26.012300Z

i don’t know what “run the query backwards” means

paulocuneo 2021-11-08T20:31:14.012500Z

relational programs can calculate the inputs given an output, it's like running it backwards

2021-11-08T20:31:57.012700Z

It might speed things up to move distincto up(very non-relational to care about ordering, but it might prune the search tree earlier)

tschady 2021-11-08T20:45:33.013Z

wow @hiredman went from 86s -> 8s with distincto first. I restarted CIDER in between, so shouldn’t be any caching.

2021-11-08T20:49:06.013200Z

I guess the membero is basically growing the search tree exponentially everytime, only for distincto to come along and prune most of it, putting distincto first means most the branches created by membero are immediately pruned

tschady 2021-11-08T22:43:42.013400Z

down to 95ms by reducing the multiple memberos per key to one with a set/intersection, good enough

tschady 2021-11-08T22:43:47.013600Z

thanks y’all

2021-11-08T22:47:24.013800Z

a great addition to to core.logic would be a clp(set) implementation

tschady 2021-11-09T18:57:58.014Z

btw, I replaced (and* (map with (everyg