clara

http://www.clara-rules.org/
2019-02-26T15:45:09.008800Z

Does anyone know if Clara supports recursion in queries? I couldn't find a mention of it on the website.

ethanc 2019-02-26T16:01:41.009400Z

@bmaddy could you elaborate on "recursion in queries"

2019-02-26T16:07:05.012900Z

Sure, be advised that I'm really new to Clara and rules engines in general. Basically, I have a tree of data (nested associatives). I want to convert that into facts to query over it. If you're familiar with Datomic, it would be like asserting an entity {:a 1 :b {:c {:d 5}}} into datoms. I then want to do a query that finds a result if :d is 5 and any ancestor has an :a with the value 1. And because I said ancestor there, I think I'll need a recursive query.

ethanc 2019-02-26T17:28:45.015900Z

Sorry for the delay You could probably brute force it in a query(iterate over the nested maps), but i would think that the idiomatic way would be to deconstruct the parent and insert the children with reference to there parent or root. for example:

(ns some.namespace
  (:require [clara.rules :as r]))

(r/defrule expansion-rule
  [?e <- :entity (some map? (vals this))]
  =>
  (doseq [v (vals ?e)
          :when (map? v)
          [k v] v]
    (r/insert! (with-meta
                 ;; wrapping ?e in a vector to prevent
                 ;; v from having a child with that is a map
                 ;; (infinite loop)
                {:key k :val v :root (or (:root ?e) [?e])}
                {:type :entity}))))

(r/defquery get-root
  [:?val :?key]
  [:entity
   (= ?key (:key this))
   (= ?val (:val this))
   (= ?root (or (first (:root this)) this))])


(-> (r/mk-session)
     (r/insert (with-meta {:a 1 :b {:c {:d 5}}} {:type :entity}))
     r/fire-rules
     (r/query get-root :?val 5 :?key :d))

ethanc 2019-02-26T17:31:33.017800Z

Using clara's fact-type here as i was being lazy and didn't want to construct a wrapper fact. By default clara uses clojure.core/type to determine rules to activate, thus i made the maps of type :entity

ethanc 2019-02-26T17:44:38.018800Z

The when in the doseq would be wrong and could be removed

ethanc 2019-02-26T17:46:12.019500Z

and would be replaced with logic to expand all the keys rather than just maps

ethanc 2019-02-26T18:12:05.022Z

The ancestors questions would then have to be post query, either that or you could insert some sort of fact representing what you are trying to join logically. Queries are aimed more at field equivalence

ethanc 2019-02-26T19:11:05.023700Z

As an example of what i mean by inserting a fact to represent what you logically want to join,

(ns some.namespace
  (:require [clara.rules :as r]))

(r/defrule expansion-rule
  [?e <- :entity (some map? (vals this))]
  =>
  (doseq [v (vals ?e)
          :when (map? v)]
    (r/insert! (with-meta
                 (assoc v :ancestors (conj (or (:ancestors ?e) []) (dissoc ?e :ancestors)))
                 {:type :entity}))))

(r/defrule query-rule
  [:query
   (= ?aq (:ancestor-question this))
   (= ?eq (:entity-question this))
   (= ?t (:query-id this))]
  [?e <- :entity]
  [:test (and (?eq ?e)
              (some ?aq (:ancestors ?e)))]
  =>
  (r/insert!
    (with-meta
      {:res ?e :query-id ?t}
      {:type :result})))

(r/defquery result-query
  [:?query-id]
  [:result
   (= ?query-id (:query-id this))
   (= ?result (:res this))])

(-> (r/mk-session)
    (r/insert (with-meta {:a 1 :b {:c {:d 5}}} {:type :entity}))
    (r/insert (with-meta {:query-id :d-5_a-1
                          :entity-question #(= (:d %) 5)
                          :ancestor-question #(= (:a %) 1)}
                         {:type :query}))
    r/fire-rules
    (r/query result-query :?query-id :d-5_a-1))
=>
({:?query-id :d-5_a-1, :?result {:d 5, :ancestors [{:a 1, :b {:c {:d 5}}} {:c {:d 5}}]}})

2019-02-26T20:21:43.028500Z

Wow, thanks for all the explanation. I must admit, I'm rather stunned. I thought rules engines would let you do datalog-like queries across data to get notified as it changes, but now it seems like you need to build data up every step of the way in order to answer your question. I was thinking about using that to help with a ton of validations on nested data I have, but that sounds like I might be better off with something like datascript. So what's the killer app for rules engines? Right now my understanding of it makes me basically think it's like calling iterate on a set of data until it doesn't change anymore (applying rules) and then filtering it (the queries). (If you don't have the time to explain, I totally understand--you're generously volunteering your time after all! Thank you!)

2019-02-26T20:45:50.029Z

@bmaddy I believe the distinction may be more around the forward-chaining vs backward-chaining strengths and weaknesses

2019-02-26T20:46:18.029300Z

Clara being oriented around the forward-chaining style rules

2019-02-26T20:47:04.030Z

and logic programming systems like prolog (core.logic too?; datascript too?) more around a backward-chaining design

2019-02-26T21:01:32.031300Z

Yeah, I've heard that. I could certainly stand to do some research to get a better grasp on that.

2019-02-26T21:49:54.034Z

@bmaddy I think it’s difficult to explain well and there are probably good write-ups out there on the differences

2019-02-26T21:50:45.035Z

Clara’s forward-chaining approach is good for a type of problem where you have a set of known facts and you have a complex chain of logical inference that can be done based on those facts to arrive at some - typically pre-enumerated set of outcomes

2019-02-26T21:51:29.035900Z

You can parameterize the queries in Clara to an extent that does allow you to look for different sorts of matches, but I don’t think the concept extends well to asking arbitrary questions about the state of the system

2019-02-26T21:51:55.036300Z

Like, I can say “Give me the outcomes that have an x value between 30-50”

2019-02-26T21:52:26.037Z

but it is harder to say, “Tell me what facts I’d need to get an outcome with x value between 30-50”

2019-02-26T21:53:09.037900Z

So Ethan had good examples above of how you can use forward-chaining to explicitly model relationships - so that you can query on those relationships-as-facts later

2019-02-26T21:53:26.038400Z

I think backwards-chaining may have more built-in approaches when that’s teh type of problem you are solving

2019-02-26T21:54:33.039700Z

Forward-chaining is nice when you are doing waht I said above, and it comes with a good mechanism of chaining along a “path” that supports arriving at your various outcomes. I don’t think anyone tends to say which is better, forward/backwards, I think it’s just that each is better at a certain class of problem.

2019-02-26T21:54:54.040200Z

But not sure why I typed all that. Probably much better explanations available online. 😛

2019-02-26T22:44:47.052800Z

Yeah, I'm a little familiar with what you're describing. I certainly have an entity (nested associative structure) that regularly changes slightly (in one case this is part of a state for a UI) and I want to run the same set of validations on it after every little change. That's what makes me think a rules engine is the way to go (i.e. my queries don't change, my data does). I just thought the querying would have something like datalog rules. I think I'll just need to study Ethan's examples a little more. I think I get the general idea it's describing, but haven't fully wrapped my head around it yet. Thanks again everyone for your help!

👍 1