This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-09-14
Channels
- # aleph (1)
- # aws-lambda (10)
- # beginners (161)
- # bitcoin (2)
- # boot (20)
- # cider (3)
- # clara (46)
- # cljs-dev (35)
- # cljsrn (9)
- # clojure (123)
- # clojure-boston (1)
- # clojure-colombia (1)
- # clojure-dusseldorf (3)
- # clojure-gamedev (2)
- # clojure-germany (2)
- # clojure-greece (1)
- # clojure-italy (7)
- # clojure-losangeles (3)
- # clojure-nl (2)
- # clojure-russia (15)
- # clojure-spec (1)
- # clojure-uk (7)
- # clojurescript (75)
- # community-development (5)
- # cursive (5)
- # datomic (25)
- # docs (3)
- # emacs (1)
- # fulcro (11)
- # graphql (131)
- # heroku (1)
- # jobs (1)
- # juxt (55)
- # lein-figwheel (2)
- # luminus (1)
- # off-topic (4)
- # om (8)
- # onyx (32)
- # pedestal (19)
- # re-frame (53)
- # reagent (11)
- # remote-jobs (1)
- # rum (12)
- # shadow-cljs (12)
- # spacemacs (53)
- # testing (2)
- # unrepl (18)
in that case I'd write a rule that should match on ANY Operation, and have a println on the RHS
@olivergeorge This is rough, but hopefully correct (thought about it quickly)
So I think the loop goes like this (only including relevant fields):
1) insert Field{:field-key :site :read-only true}
2) activate rule1
3) fire rule1
- insert FieldState{:field-key :site :read-only true}
4) activate rule2
5) fire rule2
- insert Operation{:field-key :site}
6) retract previous rule1
results since (acc/all)
changed
7) retract previous rule2
results since rule1
retracted the matching fact changed ;; due to (6)
8) retract previous rule1
results since (acc/all)
changed ;; due to (7) - no actual retraction needed here, just updates the accumulator again (6) already retracted
9) (re-)activate rule1
;; due to (8)
10) insert Field{:field-key :site :read-only true}
;; no Op1 anymore
11) repeat (4) and (5)
12) repeat (6) (7) ( 8 ) (9)
13) keep repeating (11) (12)
Ah! right. That makes sense. Thank you.
I missed the op retraction.
I wondered about having a fire-rules loop and doing (field+ops => field-state) aggregation between iterations.
I can see other possibilities.
Thanks for putting me on track
@mikerod do you know of any solid references for designing forward inference rule systems? Or some common design patterns, etc?
@dadair good question. I’m don’t really think I know a lot off the top of my head. Which is unfortunate
Is there a simple way to extract all facts from a session?
My best guess is query with an expression whose fact type is an ancestor of all records which seems to work just fine for records using [?ret <- Object]
.
@dadairI was going to suggest that perhaps the Doorenbos paper might be worth checking for references but it's 150 pages long.
One technique which might have resolved my problem is using insert-unconditional!
for Operations as that would ensure they aren't retracted (no feedback loop).
Not sure if using insert-unconditional! is considered a code smell / something to be avoided rather than common usage.
(ns spike-rules.round1
(:require [clara.rules :as rules :refer [insert! insert-unconditional! defrule defquery defsession]]
[clara.rules.accumulators :as acc]))
(defrecord Field [field-key field-state])
(defrecord FieldState [field-key field-state])
(defrecord Disabled [field-key])
(defn exists
([]
(acc/accum
{:initial-value false
:reduce-fn (constantly true)})))
(defrule rule1
"Insert FieldState based on Field and Operations"
[?field <- Field (= field-key ?field-key)]
[?disabled <- (exists) :from [Disabled (= field-key ?field-key)]]
=>
(insert!
(cond-> (->FieldState ?field-key (:field-state ?field))
?disabled (assoc :disabled true))))
(defrule rule2
"disable if read-only"
[FieldState (= ?field-key field-key) (:read-only field-state)]
[:not [Disabled (= ?field-key field-key)]]
=>
(insert-unconditional! (->Disabled ?field-key)))
(defquery export-facts [] [?ret <- Object])
(defsession form-session [rule1 rule2 export-facts])
(-> form-session
(rules/insert
(->Field :site {:value nil :read-only true}))
(rules/fire-rules)
(rules/query export-facts))
> I was going to suggest that perhaps the Doorenbos paper might be worth checking for references but it’s 150 pages long. @olivergeorge I do think that this paper is a great explanation of Rete (skipping some of the out-dated about some of the “testbed” system parts). I’m not sure it has a lot on “how to structure and design rules in practice” though.
When looking for that sort of guidance I think I’ve sort of just hunted around the internet searching for like “rules engines best practices”
I think Drools (popular Java/JVM based rules engine) had a fairly large amount of material with some general guidance on things. It’s still a search though. I wish I knew more direct sources though.
@olivergeorge I’d be careful using insert-unconditional!
if an “upstream” fact from a different rule is logically inserted and later retracted, but a downstream insert-unconditional!
has already happened, it won’t be retracted and you’ll get into an inconsistent state
So then you end up having to do things like add :salience
to try to force the rule to fire later than another and this ends up just becoming increasingly brittle
Also, loses the “declarative” sort of property that you get with rules all being under control of the truth maintenance system
Organizing rules sets - My rule set has become reasonably large within a short time. I keep getting issues with Exceptions against the schema. These are really difficult to resolve. I wonder if this is because I am running in a repl rather than outside? However, I thought I would divide my project into 4 namespaces - rules (for those I know work) worker (for any functions that help with processing my data into the working memory) queries . This then leave me with an issue about what to do with the record definitions that need to be available in all of these and in the core where I am creating the session. I either get cyclical dependencies if I put the record defs in any of these files - so I move them out into another records namespace and each of the above and the core require and refer this namespace. However when I run the mk->session if fails to find the record defs. Any ideas?
“However when I run the mk->session if fails to find the record defs. ” It sounds like the namespace defining the records wasn’t compiled then. In case you don’t know, Clojure won’t actually compile a namespace until something requires it. It can be weird in that if you have a fully qualified reference, it will resolve even if the namespace in question doesn’t require the namespace, but only if something required the namespace already. I personally think of Clojure’s compiler as a “procedural” one that takes in code and alters the state of the runtime environment accordingly.
I have a shared namespace for shared records, then I build sessions like so: (r/mk-session 'rules.shared 'rules.x 'rules.y)
you need to make sure you are requiring the namespace too before making the session, I've run into that before
so either put rules.shared
into the (ns .. (:require ..))
or do an explicit (require 'rules.shared)
Thanks - what if you have helper functions that need the shared? Do you lead them into the session as well? i.e. if a rule uses a function (private-ref? ?value) its namespace have to be made available to the session?
so for example, rules.x
, rules.y
, and rules.z
can require and use records/functions from rules.shared
, and then in some other namespace rules.engine
, you can require all the namespaces and call the mk-session function: (r/mk-session 'rules.shared 'rules.x ..)
Thanks - re-organised the project as you said. I am still getting the issue where the first record typr encountered by the rules engine in the session is saying that the Record type can't be found. This namespace stuff is so hard to handle with the level of error messages from clojure
Thanks for the tips @mikerod