Are there examples of using things like multiple insert! calls, or insert! inside of an if-let on the RHS of a rule? Or reasons why that isn't allowed? I'll try once back at a computer, but I wanted to ask in case there was something obvious I should know.
The rhs of a rule is inside an implicit let block, so it should allow for multiple inserts:
(defrule rule-name
<lhs>
=>
(insert! <fact1>)
(insert! <fact2>))
https://github.com/cerner/clara-rules/blob/main/src/main/clojure/clara/rules/compiler.clj#L454-L456
The RHS can be arbitrary clojure code, so if-lets would be fine. Though as a word of warning, don’t do stateful things in the RHS of a rule as it can lead to weird side effects.Thanks! Warning taken. I'm trying to put together a framework like the 'sensors' example in the https://github.com/cerner/clara-examples repo. I haven't worked out how I want it structured yet, but I know I want to trigger using a function to compute something based on the presence of facts, and then conditionally insert another fact based on what that function returns. Something like, "if these two facts exist, do this computation on them. If that computation yields a certain result (like a constraint violation) insert a fact that records the constraint violation. Otherwise, don't insert a fact." I'm not sure if good practice is to that computation on the LHS of the rules as a precondition, and then the RHS will trigger if result passes a test, or to do that check totally on the RHS.
Here's some pseudocode of what I am trying to decide between, not sure if both are even feasible or, as noted here, advisable:
;; pseudocode for a lhs computation. I'm not sure how to compute ?c here syntactically, if possible at all.
(defrule lhs-pseudocode
[?a <- ConditionA]
[?b <- ConditionB]
;; [?c <- compute a function return value (check-function ?a ?b)]
;; [:test ?c is truthy]
=>
(insert! (->ExceptionFact ?c)))
;; pseudocode for a rhs check
(defrule rhs-pseudocode
[?a <- ConditionA]
[?b <- ConditionB]
=>
(let [c (check-function ?a ?b)]
(when (truthy c)
(insert! (->ExceptionFact c)))))
I’d add it’s generally not good to do conditional things in RHS. The rules themselves are meant to capture the conditional flows
I generally find it an anti pattern for the RHS to “maybe do some insert”
There are exceptions
But above , you’d be better off using a :test node on LHS
Thanks, makes sense. My question then is: If I do a test, and in that test, I use a function to compute a value which is to be checked, and I want to pass that value on to the RHS as part of the fact insert, what would that look like? In the first pseudocode above, I want to pass the output value of (check-function) to be inserted as a field in a record on the RHS...
The one way I thought to get around that was to not check conditions at all in the rules, but always insert a fact with the results of the computation I want to do, and that record has the result. And then, during a query, I can filter out those new records based on some flag/indicator field of the result. That seems like I potentially put more facts into the session than are necessary.
Another thought I was to follow up what I did above, but then add another rule that prunes facts that don't have a certain value in an indicator field -- like a cleanup rule. Then there is no conditional inserts in the LHS or RHS of a rule, but another rule takes care of the conditional inserts by removing things that I would not have originally inserted.
Again, new to this, so I am trying to map the ideas of how I want my conditional domain logic I want to the best practices and discipline of a rules system...
@matthew.pettis I’d recommend a rule that always inserts facts modeling result values you want like that. You sort of already said this above
So the answer is just more rules. More “intermediate facts” representing knowledge of the system
I can’t write up too many useful details at the moment though. So I may not be that useful to you
If you make queries more specific. You don’t have to worry as much about adding more intermediate level facts though
And don’t typically need cleanup. Unless you have some memory concern or something. That can be somewhat trickier to retract things in a way that plays well with truth maintenance
As mike has mentioned(outside of this thread) LHS is probably the ideal place for this sort of logic. We can continue that conversation there. :)
@mikerod Thanks! The more I thought about my proposed alternatives, the more it seemed to be in line with the rules philosophy. I'll be trying that. I appreciate the thought of writing something up too. I think I have enough to go on to code to this philosophy to start with, and I'll probably have other questions later. Thanks again!
Thanks! Yep, Or, for my first pass, with my logic, I'm going to not do conditional inserts within rules -- I'm going to try to just do do inserts with a record of a natural structure that allows me to pull out what I want on a query, and put logic in the query, or even post-process query outputs. Thanks for the help!
Also, from a thread way-back... I'd like to try clara.tools, I see the repo here: https://github.com/rbrush/clara-tools ... what I don't see is the :dependencies version string to put into my project.clj file... and advice on how to install from this repo?