Fork me on GitHub
#clara
<
2022-09-29
>
ixemad10:09:07

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?

ethanc14:09:26

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.

ixemad15:09:04

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, @U3KC48GHW

👍 2
ethanc15:09:13

should be noted, i didnt account for cycles…

ixemad16:09:51

Yeah. That is something I will have to find out how to tackle in order to represent updates in my model.