Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Clara tiered fact update rules
(require '[clara.rules :as r])
;;;; Define 3 rules, where the "priority" order is r1, r2, r3, where the highest priority is first
;;;; and the rest is in descending order of priority.
;;;; :type :rule/result "syntetic" fact is used to hold the final changes that can be queried out
;;;; from a session after `r/fire-rules` via `r/query` on the `find-results` query.
;;;; A namespace qualified keyword is used to avoid collision with externally given :type of
;;;; "real" facts.
;;;; To ensure the priority order of rules is upheld each lower priority rule checks that no higher
;;;; priority rule has already fired. This common pattern can be broken out by a higher-level rule
;;;; generation scheme if it becomes difficult to maintain.
;;;; The original post problem statement does not describe whether the facts have any sort of identifier
;;;; on them. Since this is an unknown the :id stored on the :rule/result is the whole fact. The :id
;;;; is used to ensure the same fact is not updated twice by rules of different priorities.
(r/defrule r1
[?f <- :type-a [{:keys [value]}] (= value :x)] ;; <- looks for :value :x's
(r/insert! {:type :rule/result
:from :r1
:id ?f
:fact (assoc ?f :key-from-r1 1)}))
(r/defrule r2
[?f <- :type-a [{:keys [value]}] (= value :y)] ;; <- looks for :value :y's
[:not [:rule/result [{:keys [from id]}]
(= from :r1)
(= ?f id)]]
(r/insert! {:type :rule/result
:from :r2
:id ?f
:fact (assoc ?f :key-from-r2 2)}))
(r/defrule r3
[?f <- :type-a] ;; <- catch all facts not matched by r1 or r2
[:not [:rule/result [{:keys [from id]}]
(contains? #{:r1 :r2} from)
(= ?f id)]]
(r/insert! {:type :rule/result
:from :r3
:id ?f
:fact (assoc ?f :key-from-r3 3)}))
(r/defquery find-results []
[:rule/result [{:keys [fact]}] (= ?fact fact)])
;;; If you are unfamiliar with the :fact-type-fn for `r/mk-session` see
(def fact-type-fn :type)
(def s
(r/mk-session [r1 r2 r3 find-results]
:fact-type-fn fact-type-fn))
;;; :value :x matches `r1`
;;; :value :y matches `r2`
;;; :value :z (any non :x or :y value) matches `r3`
(let [qres (-> s
(r/insert {:type :type-a
:value :x
:tstamp 1}
{:type :type-a
:value :y
:tstamp 2}
{:type :type-a
:value :y
:tstamp 3}
{:type :type-a
:value :z
:tstamp 4})
(r/query find-results))]
(mapv :?fact qres))
;; Returns
[{:type :type-a, :value :x, :tstamp 1, :key-from-r1 1}
{:type :type-a, :value :y, :tstamp 3, :key-from-r2 2}
{:type :type-a, :value :y, :tstamp 2, :key-from-r2 2}
{:type :type-a, :value :z, :tstamp 4, :key-from-r3 3}])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment