clara

http://www.clara-rules.org/
Mario C. 2021-06-09T15:24:52.049800Z

If you are not going to use bindings in the LHS and only use them in the RHS; Is it okay to just bind the whole fact and then pluck what you need from that fact binding on the RHS. Or is the better approach to just bind everything that you need in LHS to be used in the RHS? Is it a matter of style only or are there trade offs Im not aware of? I lean on the former since it makes the rule's LHS more "digest-able" instead of seeing 10 lines of bindings that dont really do much in terms constraints/conditions

2021-06-09T15:38:23.050700Z

@mario.cordova.862 As long as you are only using the binding on the RHS and you are not using an accumulator - I think there isn’t a lot of functional difference in terms of the engine operation.

2021-06-09T15:38:41.051200Z

If you are using an accumulator, you have to be aware of the “unusued” bindings actually forms “partitions”

2021-06-09T15:39:02.051700Z

[?all <- (acc/all) :from [X (= ?x x)]]

2021-06-09T15:39:22.052200Z

that’d actually “partition” the accumulations by their x values

2021-06-09T15:39:42.052500Z

So it looks “unused” perhaps, but really it semantically matters.

2021-06-09T15:40:46.053800Z

If you ignore that - perhaps not related to your particular usage you have in mind: I’d still favor being more explicit in the LHS because the LHS is in general more structured. It has more ability to be parsed/analyzed by tooling. Everything you do in the RHS is basically just free-form clj forms.

2021-06-09T15:41:09.054300Z

So my default preference is to express as much as possible to as much granularity as possible in the LHS and minimize the amount the RHS does

Mario C. 2021-06-09T16:06:58.059400Z

Is that assuming that ?x was already binded with some other fact? Or if that was all there was to the LHS?

ethanc 2021-06-09T16:07:32.059900Z

Might also be worth noting that using bound fields could also be more performant in certain scenarios, ex.

(defrule some-rule 
  [?x <- X]
  [?y <- Y (= y-field (:x-field ?x))]
  =>
  (<RHS things>))
vs
(defrule some-rule 
  [X (= ?x-field x-field)]
  [?y <- Y (= y-field ?x-field)]
  =>
  (<RHS things>))
Probably not a super huge deal for small rules networks, but the latter would likely produce a HashJoin node rather than an ExpressionJoinNode. ExpressionJoinNodes, in my experience, have a bit more overhead (minuscule) due to additional function evaluations and invocations.

Mario C. 2021-06-09T16:09:57.061800Z

(defrule my-rule
  [:x/y :from [{:keys [a b c d e f g h i j]}] (= a ?a) . . . (= j ?j)]
  =>
  (insert! {:data {:my-a ?a ... :my-j ?j}}))

Mario C. 2021-06-09T16:10:02.062Z

This is what I had in mind

Mario C. 2021-06-09T16:10:42.062600Z

vs

(def my-rule 
  [?xy <- :from [:x/y] ...)

Mario C. 2021-06-09T16:11:38.063800Z

(ignore syntax errors)

ethanc 2021-06-09T16:14:22.066200Z

As Mike mentioned the top rule will perform a partitioning action, with potentially different behavior. If the number of fields being bound is equivalent to the number of fields of the fact, then simply binding the entire fact probably makes just as much sense.

Mario C. 2021-06-09T16:20:41.067600Z

hmm very interesting, I did not know that. Thanks for the input @ethanc @mikerod. Will need to do some exploring then

Mario C. 2021-06-09T16:47:58.069600Z

(defrule binded-rule
  [:foo/bar [{:keys [id first-name last-name]}]
   (= id ?id)
   (= first-name ?first-name)
   (= last-name ?last-name)]
  =>
  (println "fire bind")
  (insert! {:fact/type  :binded/person
           :id         ?id
           :first-name ?first-name
            :last-name  ?last-name}))

(defrule unbinded-rule
  [?person <- :foo/bar]
  =>
  (println "Fired unbind")
  (insert! {:fact/type  :unbinded/person
            :id         (:id ?person)
            :first-name (:first-name ?person)
            :last-name  (:last-name ?person)}))

(defrule binded-acc-rule
  [?persons <- (acc/all) :from [:foo/bar [{:keys [id]}] (= id ?id)]]
  =>
  (println "BINDED ACC:")
  (println ?persons)
  (insert! {:fact/type  :binded/acc
            :some :foo}))

(defrule unbinded-acc-rule
  [?persons <- (acc/all) :from [:foo/bar]]
  =>
  (println "UNBINDED ACC:")
  (println ?persons)
  (insert! {:fact/type  :unbinded/acc
            :some :bar}))
The first two didn't really see any difference aside from the binded rule having constraints. But the last two def had difference. The binded-acc-rule fired 3 times for each 3 facts and the unbinded just fired once, accumulating all 3 facts. I understand now what you meant by partition

2021-06-09T17:08:58.069900Z

@mario.cordova.862 yes the accumulator cases is waht I was mentioning

2021-06-09T17:09:06.070200Z

Accumulators (necessarily) have to be “partitioned” by the binding

2021-06-09T17:09:33.070800Z

To understand why, consider this

[?persons <- (acc/all) :from [:foo/bar [{:keys [id]}] (= id ?id)]]
=>
(println [?id ?persons])

2021-06-09T17:09:55.071600Z

Notice how if you were referring to the ?id binding itself - it would need to have a single-stable value for that accumulation of facts?

2021-06-09T17:10:19.072100Z

So (acc/all) must only work across “equal join binding partitions”

2021-06-09T17:10:25.072400Z

The same goes for any accumulator

2021-06-09T17:12:29.073900Z

Also, as Ethan said https://clojurians.slack.com/archives/C08TC9JCS/p1623255042062600 and I just left out since you said “unused LHS bindings” - I’d certainly favor direct field bindings in join cases due to the rules engine taking advantage of that visibility into the lower-level of granularity

2021-06-09T17:12:44.074300Z

I can understand how

(defrule my-rule
  [:x/y :from [{:keys [a b c d e f g h i j]}] (= a ?a) . . . (= j ?j)]
  =>
  (insert! {:data {:my-a ?a ... :my-j ?j}}))
may be a bit tedious though

2021-06-09T17:13:11.074800Z

You could consider perhaps wrapping some sort of helper over it

2021-06-09T17:13:39.075400Z

Or just bind to the whole fact, but know there are these certain caveats and also the thing I said about expressing as much as possible in LHS

Mario C. 2021-06-09T17:19:17.077600Z

Yea when I mentioned the problem I wasn't referring to accumulators but learning this bit actually made me "read" the rules differently now. Always had trouble wrapping my head around having a binding in an accumulator. "If its accumulated which id is the ?id tied to??" but now I see that it fires once for each

1👍