@mikerod @alex-dixon I see that there was some discussion of patterns for updating facts; just a heads up/reminder of the “cancelling” functionality added in https://github.com/cerner/clara-rules/issues/249 and documented at https://github.com/cerner/clara-rules/blob/master/src/main/clojure/clara/rules.cljc#L38 It doesn’t really solve the hassles of updates though, more of a perf optimization. Also, we haven’t ended using it at Cerner yet due to the work involved in testing, so there could well be problems I missed.
Thanks for bringing this up again. This is really exciting. I only skimmed 249 but will review it more later. If I’m understanding correctly the modify would increase performance because it would basically be “mutable data structures”. I really, really at the same time don’t like the sounds of that …but if it allows us to easily trade immutability for performance in certain situations then it seems like a great feature
The mutability is purely internal to Clara, not user-facing. Or at least if it is user-facing that would be a bug. 🙂
Similar to the way the memory is mutable internally, but that isn’t shown to the user
Ah! Ok. I was thinking about a (modify!)
which I think was discussed if only briefly
@wparker yes, this thing you wrote came to mind and then I realized you actually had parts of it already merged to master. I forgot you didn’t just have it on a branch.
However, it is not the same thing as the update!
or modify!
proposal I was considering.
I think there is likely some value for the engine to explicitly “understand” the idea of doing a RHS retract + insert of an entity that logically represents the “same fact”.
Reasoning is: 1) RHS retract is clunky right now and even worse has some fairly broken behavior with truth maintenance/batched logical insertion etc. 2) Optimizations similar to your “update facts” optimizations posted above here may also be applicable.
Drools does have some optimization to the path of modifying a fact in place - via some of the property reactive hooks. I think Drools also has an explicit “modify” sort of statement that gives it awareness of a fact in working memory that needs to change.
In our case, we’d not do any in-place mutation of course (structural sharing is good though). I think something like a modify!
or, I prefer, update!
could be done in a way that was sure to have well defined behavior with the truth maintenance as well as batching behavior of logical inserts etc
I think the update!
scenario is one of prominent use-cases for retract!
on the RHS.
Although I think there are other reasons to retract, such as “no longer relevant event” scenarios perhaps, so retract!
isn’t completely useless still (and needs to get fixed to behave sanely with batched inserts and truth maintenance).
Yeah, I realize there’s some other stuff being discussed. I’ll have to read up on those features in Drools. Re updates and truth maintenance - it isn’t clear to me what sorts of semantics would make sense to manually mess with a logically inserted fact. My intuition there is that manual modification to something that is logically derived doesn’t make sense, since if A implies B, then A should continue to imply B until/unless A changes.
I realize that isn’t the current implementation though - I’ve toyed with changing things so that a RHS retract of a logically inserted fact simply wouldn’t happen in the same way that an external retract of a fact not in the session doesn’t do anything. The fact that it does do something now always seemed like a leaky impl detail/bug to me actually
I think there is a lot of value to be had in the ability to “update a fact”
Which would require something like a retract/insert at some level conceptually to the rest of the network - something may not be affected by the thing that changed though
and with truth maintenance and all logical inserts (something I think is very valuable), it gets near impossible to actually retract something because you immediately invalidate it against the truth maintenance
I think something liek this:
(defrule update-it
[?a <- A]
=>
(update! ?a #(update % :value inc)))
The truth maintenance should not have an issue with this. and it should propagate and participate in the truth maintenance downstream as expected
more interesting even
(defrule update-it
[?a <- A (= value 10)]
=>
(update! ?a #(update % :value inc)))
Here the update!
actually contradicts the LHS condition - I still think that should be okIt should participate in a well-defined way with the truth maintenance is the point.
Then there would have to be considertaion of something like:
(defrule update-it
[?a <- A (= value 10)]
=>
(update! ?a #(update % :value inc))
(insert! (->B)))
Here you have a new fact that relies on truth maintenance and this rule as it’s “logical support”. It’s a tougher case to consider due to the LHS no longer being true. I’m thinking the token should be constructed in a way taht makes the updated ?a
support the new B
I might agree that retraction of a logically derived fact doesn’t make sense. You could always just retract the “root facts” that caused it. I do think that would make sense to be able to be done in the RHS and behave as expected with respect to the batched insertions of other rules in the same activation group though
This one is unrelated a bit to our other thread concerning update!
.
It shouldn’t be too difficult to have Clara do something like “unlinking” conditions from evaluation when required prior conditions haven’t been satisfied yet.
A good example of the problem is something like:
(defrule do-things
[A (= ?id id)]
[B (= ?id id) (expensive-constraint x)]
=>
(insert! (->C)))
Clara will currently do the (expensive-constraint x)
when B
s are inserted, with no A
in working memory at all, even if this condition is only used in this ruleYou can write rules to avoid it like:
(defrule find-things
[A (= ?id id)]
[?b <- B (= ?id id)]
=>
(insert! (->ReadyB ?b)))
(defrule do-things
[ReadyB (expensive-constraint x)]
=>
(insert! (->C)))
However, that is not super obvious and also may not always be so straightforward to correctly split
By “batched insertions” do you mean the stuff in https://github.com/cerner/clara-rules/issues/229 ?
If you have ideas on how to implement that in a simple way I’d be interested. 🙂 It seems like it could get complicated though.
On a possibly similar note, I’ve toyed with changing the structure where a rule only has one parent before - if there are independent conditions, they don’t inherently need to propagate through each other. That would be a lot of work to change though
yep
I think the case you are describing is interesting. I’m not sure how great it is to have a rule with unrelated conditions, but I know that idea comes up for doing things like aggregations onto a larger “aggregated fact”
In terms of what I mentioned above, I’m not sure it’d be that difficult
I am at least thinking about it like:
1) during compilation, track if rule conditions rely on others
Hmm -- the railroad diagram link in https://github.com/cerner/clara-rules/wiki/guide appears to be broken.
2) during rule evaluation, avoid testing conditions if their upstream dependencies haven’t yet had any matches. Just batch the facts to test “later”
3) the batch is “flushed” when the condition is reached via the left activation
Negation nodes sort of work by batching fact evaluation already
I think it may not be too terribly difficult at least. Of course I haven’t tried at all yet 😛
@tcarls Thanks. All of that content now lives over on the web site at http://www.clara-rules.org/docs/rules/, so we need to delete/redirect those wiki pages.