I've just started exploring with Clara, and don't have a ton of experience with rules systems, and I've run into a surprise. When I have a rule something like [Foo (= x 1)] (not [Foo (= x 2)]) => (insert! (->Foo 2))
it goes in to an infinite loop
@hiredman insert! Is a conditional insert. These inserts are monitored by a “truth maintenance system” (aka TMS). The goal of the TMS is to keep all of the rules and their consequences in a logically consistent state
So the rule you put above is a logical contradiction.
There are workarounds. Depending on what it is you are trying to achieve.
Also the TMS is useful to be able to express large numbers of rules in a declarative/non order dependent way. You don’t have to worry about “when something was inserted” to be careful to not check for it “before it was inserted” and that sort of thing. It leads to a less brittle system.
The concept of a TMS has been around in many of the more modern rule systems.
@mikerod thanks, makes sense, I hadn't connected those dots
@hiredman I will say that the engine going into an infinite loop without trying to help isn’t really user-friendly.
The action a rule does on the right-hand side (aka RHS) of the =>
is arbitrary from the rules engine perspective, so it cannot “know it is in an infinite loop”. However, something could reasonably be added to allow a configurable upper limit on the times you’d allow what may “look like a loop” to be allowed until the engine halted and reported a possible error. For now though, it could be documented as a FAQ or something as well.
eg “If the engine seems to be in an infinite loop, this is commonly the cause…”
There are cases that you may think it is hard to see how to not need a rule like you have above. Some of those can actually be tricky to express, but in many cases that I’ve seen, accumulators can be used to achieve the semantics desired, or a new fact type that is used for the derived fact that is different from what is deriving it eg instead of
[:not [Foo (= x 2)]] => (insert! (->Foo 2))
Something like
[:not [Foo (= x 2)]] => (insert! (->DerivedFoo 2))