Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you use core.logic in a practical way in a larger Clojure program?

I'm having trouble wrapping my head around how to mix clojure and core.logic.

For example say I have the following problem:

I have a list of key value pairs with attached scores:

(:foo "10" 2)
(:bar "20" 3)
(:baz "30" 7)

And I also have a map:

{:foo "10",
 :bar "42",
 :baz "30"}

What I'd like to do, is return a list of scores based on the list of scores evaluated in terms of the map.

Using core logic I can do something like this:

(defrel score key value score)
(fact score :foo "10" 2)
(fact score :bar "20" 3)
(fact score :baz "30" 7)

(run* [q]
  (conde 
    ((score :foo "10" q))
    ((score :baz "30" q))))

And I get the intended result:

(2 7)

My problem is I don't see how to turn this into something that I can run in a larger program dynamically. Meaning that I will have different maps and different constraints to apply at different times. I think I can create the argument to conde by writing a function that takes the map and outputs the constraints, but how do I have that run* evaluate in the context of a set of temporary facts?

I could certainly write a function to return what I want without core.logic, but that seems less elegant. Maybe I'm barking up the wrong tree (I'm new to both Clojure and core.logic) and this isn't a constraint problem at all.

So my question are:

How do you mix in core logic when you're pulling your facts and constraints from a sources you won't know until runtime?

And related, how do you do so in an evironment where you want to evaluate a set of constraints within a new environment of facts?

like image 830
jgerman Avatar asked Mar 07 '13 00:03

jgerman


2 Answers

The most important thing to remember is this: Relations are just function which return a goal. A goal is a function which can succeed or fail, so basically relations are just higher order function.

Now you can make your example such that the relation and the various facts are in a single function and there is no "global" relation/facts which can interfere with each other:

(defn find-things []
  (letfn [(scoref [key value score]
            (conde
             [(== key :foo) (== value "10") (== score 2)]
             [(== key :bar) (== value "20") (== score 3)]
             [(== key :baz) (== value "30") (== score 7)]))]
    (run* [q]
          (conde 
           ((scoref :foo "10" q))
           ((scoref :baz "30" q))))))

score is just a function which return a goal (using the conde macro)

This solves the local/global relations problem, but still the facts and query are hardcoded into the function which we want to be passed as in parameters. One possible way to do that is understand the core.logic APIs which allows you to define dynamic logic vars and unify them, etc. I have not been through that API so I won't be able to answer using that. Another way would be to use macro and eval magic:

(defmacro find-things-generator [data query]
  (let [key (gensym) value (gensym) score (gensym) q (gensym)]
    `(letfn [(~'scoref [~key ~value ~score]
               (conde
                ~@(map #(-> [`(== ~key ~(% 0))
                             `(== ~value ~(% 1))
                             `(== ~score ~(% 2))]) data)
                ))]
       (run* [~q]
             (conde
              ~@(map #(-> [`(~'scoref ~(% 0) ~(% 1) ~q)]) query)
              )))))


(defn find-things [data query]
  (eval `(find-things-generator ~data ~query)))

(def data [[:foo "1" 2]
           [:bar "2" 3]
           [:baz "3" 7]])

(def query {:foo "1",
            :bar "2",
            :baz "3"})

(find-things data query)
like image 169
Ankur Avatar answered Oct 24 '22 15:10

Ankur


I had a similar question and here is what I came up with, translated to your problem.

Define your collection of scores.

(def scores
  [[:foo "10" 2]
   [:bar "20" 3]
   [:baz "30" 7]])

Next, define a function that will convert the scores into relational form.

(defn scoreso [key value score scores]
  (conde
    [(fresh [a]
       (firsto scores a)
       (== [key value score] a))]
    [(fresh [d]
      (resto scores d)
       (scoreso key value score d))]))

Finally, determine which scores in the vector match given keys and values.

(run* [score]
  (fresh [key value]
    (scoreso key value score scores)
    (conde
      [(== key :foo) (== value "10")]
      [(== key :baz) (== value "30")])))

This results is (2 7).

The query is phrased differently, but it is equivalent.

like image 43
Steve Sloan Avatar answered Oct 24 '22 15:10

Steve Sloan