Fork me on GitHub
#clara
<
2017-08-29
>
dadair17:08:59

I have a shared entry point into a rule session where a namespace associated with specific rules/queries can be applied:

(defn run-rules
  "Runs a rule session given a rule namespace, a root state tree node, and a performed action."
  [ns root action]
  (let [session (-> (r/mk-session 'justice.rules.shared ns)
                    (r/insert (->Tree (walk/prewalk tree/augment-if-node root))
                              (->Action action))
                    (r/fire-rules))]
  ...
However, I get a No namespace exception when I pass a namespace in eg. (run-rules 'justice.rules.x ..). I've "fixed" the issue by adding (require ns) inside run-rules, but is there a better way to accomplish this? I could for example just require the namespaces in the ns declaration, or I could have a whitelist of namespaces to require, but I'd like to keep adding new namespaces to be as simple as creating a new namespace and passing it in to the generic shared entrypoint.

mikerod19:08:36

> Thanks! I was struggling because for this specific problem coming up it was not clear how to create facts that are [:not [B (= ?id id)]] @iku000888 sorry for long delay, just saw your response. I’m not sure what you mean to create a fact to match a negation (ie condition with a :not). The negation condition is used in conjuction with the [A (= ?id id)] in my example. This together forms a “join” sort of query for all A that do not have a matching B. So back to that example, repeating for clarity:

(r/defrule joins
  [A (= ?id id)]
  [B (= ?id id)]
  =>
  (r/insert! (->C)))

(r/defrule nil-joins
  [A (= ?id id)]
  [:not [B (= ?id id)]]
  =>
  (r/insert! (->C)))
The rule names may not be the best, but sticking with them: * The rule joins is for the typical “inner join” matches - it matches A with Bs they go with * The rule nil-joins is for the “outer left” part of the join - it keeps the A results that do not have any actual join with a B This may not be the specific issue you had, but that was my interpretation of your “left-join” type of rule question. Given that you have something with retraction working, I’m thinking this is a different problem you are wanting to solve.

mikerod19:08:45

@dadair Clojure or ClojureScript?

mikerod19:08:00

Either way though, r/mk-session doesn’t attempt to require namespace for you.

mikerod19:08:39

I’m not positive that would be a great idea either. Best practice in clj would be to be sure to appropriately declare the namespace you want to have compiled - most preferably in the ns header :requires

mikerod19:08:16

You could get away with a dynamic require call in your run-rules though. I’ve had use-cases doing that before to load rules from arbitrary namespaces

mikerod19:08:37

I don’t think there is a “better way to accomplish it” though in terms of what Clara currently offers.

mikerod19:08:51

It expects things to already be compiled

dadair19:08:58

@mikerod thanks, this would be regular clojure. I've moved away from dynamic require, as it was adding unnecessary compilation overhead (in terms of performance). I'm still using a ns-whitelist var though rather than using the (ns .. (:require ..)) form as I've had some formatters remove the references as they are "unused".

mikerod19:08:47

Yeah, if you wait to do a require in the fn, it could be a pretty big slowdown depending on the size of it

mikerod19:08:02

and perhaps could be better suited at “compilation time” with whatever else you have to start up the app

mikerod19:08:41

Not to get too lost in the weeds, but Clara does have a few other alternatives instead of loading whole ns’s of rules

mikerod19:08:41

For example, you can provide collections of rules to r/mk-session

(defrule r1 <etc>)
(defrule r2 <etc>)
(defrule r3 <etc>)

(def rule-group1 [r1 r2 r3])

(defrule r4 <etc>)
(defrule r5 <etc>)
(defquery q1 <etc>)

(def rule-group2 [r4 r5 q1])

(defrule r6 <etc>)


<....>

(r/mk-session rule-group1 rule-group2 [r6])

wparker18:08:48

@dadair Agreed with @mikerod ‘s previous comments. I wanted to add that namespace compilation can have arbitrary effects in Clojure; one notable one is protocol extensions. That is, a linter removing namespace whose vars you aren’t directly using from your requires could be dangerous to things other than Clara. Also, note that require will short-circuits if the namespace already exists unless you specifically request reloading, so the performance question is really whether you pay the cost at initialization of your Clojure environment or the first time you build the relevant session.

mikerod19:08:01

so you can pick in choose at different granularities is my point