Does anyone know if Clara supports recursion in queries? I couldn't find a mention of it on the website.
@bmaddy could you elaborate on "recursion in queries"
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.
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))
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
The when in the doseq would be wrong and could be removed
and would be replaced with logic to expand all the keys rather than just maps
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
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}}]}})
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!)
@bmaddy I believe the distinction may be more around the forward-chaining vs backward-chaining strengths and weaknesses
Clara being oriented around the forward-chaining style rules
and logic programming systems like prolog (core.logic too?; datascript too?) more around a backward-chaining design
Yeah, I've heard that. I could certainly stand to do some research to get a better grasp on that.
@bmaddy I think it’s difficult to explain well and there are probably good write-ups out there on the differences
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
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
Like, I can say “Give me the outcomes that have an x value between 30-50”
but it is harder to say, “Tell me what facts I’d need to get an outcome with x value between 30-50”
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
I think backwards-chaining may have more built-in approaches when that’s teh type of problem you are solving
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.
But not sure why I typed all that. Probably much better explanations available online. 😛
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!