Hi all! I was wondering how you manage the LHS complexity when you need to detect some useful path. Specifically, I will have a lot of rules that will trigger if a given "path" is detected
(defrule do-this
[ FactA (= ?id-a (:id this))
(= ?path-parameter (:some-attribute this)) ]
;; a path of facts
[ FactB (= ?id-b (:id this))]
[ FactC (= ?id-c (:id this))
(= ?id-b (:parent this))
(> 30 (:attr-x this)) ]
[ FactD (= ?id-d (:id this))
(= ?id-c (:parent this)) ]
[ FactE (= ?id-e (:id this))
(= ?id-d (:parent this)) ]
=>
(do-something)
)
Ideally, I would like something like this
(defrule do-this
[ FactA (?id-a (:id this))
(= ?path-parameter (:some-attribute this)) ]
[:test (detect-path ?path-parameter) ]
=>
(do-something)
)
Is that possible? What would be your best approach?I would be tempted to model the “path” portion as a Fact of its own, something like:
(defrecord Ancestry [ancestors key-attributes])
(defrule ancestry
[ FactB (= ?id-b (:id this))]
[ FactC (= ?id-c (:id this))
(= ?id-b (:parent this))
(> 30 (:attr-x this)) ]
[ FactD (= ?id-d (:id this))
(= ?id-c (:parent this)) ]
[ FactE (= ?id-e (:id this))
(= ?id-d (:parent this)) ]
=>
(insert! (->Ancestry {<map/tree ancestry data>} {<high level attributes from ancestors>})))
(defrule do-this
[ FactA (?id-a (:id this))
(= ?path-parameter (:some-attribute this)) ]
[Ancestry (some-logic this ?path-parameter)]
=>
(do-something))
Granted that approach works on the assumption that the ancestry is a strict set of facts B->E. If something more flexible was needed, i suppose a modification could be made:
(definterface Ancestor
(id [] "the facts id")
(parent [] "the parents id")
(attributes [] "map of attributes"))
(defrecord FactA [id some-attribute])
(defrecord FactB [record-id parent-id attr-x]
Ancestor
(id [this] record-id)
(parent [this] parent-id)
(attributes [this] {:attr-x attr-x}))
(defrecord FactC [record-id parent-id attr-y]
Ancestor
(id [this] record-id)
(parent [this] parent-id)
(attributes [this] {:attr-y attr-y}))
(defrecord FactD [record-id parent-id attr-z]
Ancestor
(id [this] record-id)
(parent [this] parent-id)
(attributes [this] {:attr-z attr-z}))
;<More Facts>
(defrecord Ancestry [id parent important-attributes])
(r/defrule progenitor
[?progenitor <- Ancestor (nil? (.parent this))]
=>
(r/insert! (->Ancestry (.id ?progenitor) nil (.attributes ?progenitor))))
(r/defrule ancestry
[?ancestor <- Ancestor
(= ?parent (.parent this))
(not= nil (.parent this))]
[?ancestry <- Ancestry
(= (.id this) ?parent)]
=>
(r/insert!
(->Ancestry
(.id ?ancestor)
(:parent ?ancestry)
(merge
(:important-attributes ?ancestry)
(.attributes ?ancestor)))))
(r/defrule do-this
[ FactA
(= ?id-a (:id this))
(= ?path-parameter (:some-attribute this)) ]
[?ancestory <- Ancestry (some-logic this ?path-parameter)]
=>
(r/insert! (->Success ?id-a ?ancestory)))
(r/defquery find-it
[]
[?q <- Success])
(defn test-session
[]
(-> (r/mk-session)
(r/insert (->FactA "1" 32))
(r/insert (->FactB "2" "3" 12))
(r/insert (->FactC "3" "4" 18))
(r/insert (->FactD "4" nil 32))
r/fire-rules
(r/query find-it)))
This assumes you own the definition of the facts, but can generate the ancestry of the facts.After writing the question I was also considering that approach (not so elaborated, though) as the best. With your response, I now feel more confident to move in that direction. Thank you, @ethanc
should be noted, i didnt account for cycles…
Yeah. That is something I will have to find out how to tackle in order to represent updates in my model.