Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no-loop option for rules #23

Closed
sitch opened this issue Oct 31, 2013 · 13 comments
Closed

Add no-loop option for rules #23

sitch opened this issue Oct 31, 2013 · 13 comments
Milestone

Comments

@sitch
Copy link

sitch commented Oct 31, 2013

I get some strange behavior when I have circular types of rules:

(defrule optimist
  [?g <- Glass (= ?full full) (= ?capacity capacity)]
  [Faucet (= ?flow flow)]
  [:test (not= ?full ?capacity)]
  =>
  (retract! (->Glass ?full ?capacity))
  (insert! (->Glass (+ ?full ?flow) capacity))
  (println ?g))

I was hoping this would work something like:

{:capacity 100 :full 0}
{:capacity 100 :full 25}
{:capacity 100 :full 50}
{:capacity 100 :full 100}

I was wonder what your thoughts are on handling a rule like this

@rbrush
Copy link
Contributor

rbrush commented Nov 1, 2013

Clara uses logical inserts in rules, so if the conditions that caused a rule to fire become false, the inserted fact is automatically retracted. This is the desired behavior in most use cases (or at least the ones I had in mind when designing this). For instance, if we insert a Temperature of 20 degrees and a rule declares that as Cold, we want to retract the Cold fact if the inserted Temperature is retracted. In this case, since we're retracting the Glass fact that triggered the rule, it also retracts facts the rule inserted itself.

Of course, that doesn't help this scenario. I know other rules engines support logical and unconditional inserts, so we could address this by doing the same. It would be straightforward to add an insert-uncoditional!, that would not retract facts it inserts in the event the instance of the rule that fired becomes false.

@sitch
Copy link
Author

sitch commented Nov 1, 2013

insert-unconditional! would definitely be helpful in a few of the use-cases that I have.

rbrush pushed a commit that referenced this issue Nov 2, 2013
@rbrush
Copy link
Contributor

rbrush commented Nov 2, 2013

Added an insert-unconditional! function as described here. I haven't verified your specific use case now works, but this functionality made sense and is tested in a simpler unit test.

@sitch
Copy link
Author

sitch commented Nov 4, 2013

So one other feature that would help out would be the 'no-loop' concept from drools, where you can specify whether the rule will refire if the conditions are met after retracting/inserting a new fact in the rhs.

The above example would look something like this:

I get some strange behavior when I have circular types of rules:

(defrule-no-loop optimist
  [?g <- Glass (= ?full full) (= ?capacity capacity)]
  [Faucet (= ?flow flow)]
  [:test (not= ?full ?capacity)]
  =>
  (retract! (->Glass ?full ?capacity))
  (insert! (->Glass (+ ?full ?flow) capacity))
  (println ?g))
{:capacity 100 :full 0}

@rbrush
Copy link
Contributor

rbrush commented Nov 5, 2013

If you wouldn't mind, I wonder if you could describe your use case in a bit more detail? Adding no-loop is doable but I'd like to understand the problem space a bit further in case there may be other (possibly cleaner) changes we can make to support it.

@sitch
Copy link
Author

sitch commented Nov 6, 2013

Example for no-loop from the drools book would be:

(defrule add-interest
  [?a <- Account (= ?balance balance) (= ?interest)]
  =>
  (retract! ?a)
  (insert! (compound ?balance ?interest))

LHS and RHS share a fact that is altered non-recursively

@rbrush
Copy link
Contributor

rbrush commented Nov 6, 2013

Fair enough. We can probably do this with a relatively simple change:

  1. Add properties to the rules in the form of a map in defrule, one of which can be a :no-loop tag.
  2. When the production node is left-activated, push that node onto a "rule stack" in the fire context (which is simply a dynamic var.)
  3. Check if nodes are on the top of the rule stack when being left-activated, ignoring the activation if they are.

I'll plan on jumping into this as time permits…it might be a week or so due to other demands. Of course, I'd be happy to work with a patch if anyone was interested in taking a stab at it.

@rbrush
Copy link
Contributor

rbrush commented Nov 10, 2013

The previous commit adds :no-loop support to master. The example rule below shows this in action, retracting a temperature and adding a new one one degree cooler, and does not re-trigger the rule since it is :no-loop.

(defrule reduce-temp-one-degree
  "Example rule to reduce temperature."
  {:no-loop true}
  [?t <- Temperature (== ?v temperature)]
  =>
  (do
    (retract! ?t)
    (insert-unconditional! (->Temperature (- ?v 1) "MCI"))))

I also deployed a 0.3.0-SNAPSHOT build of this logic to the Sonatype snapshot repository.
at https://oss.sonatype.org/content/repositories/snapshots/ for convenience.

@rbrush
Copy link
Contributor

rbrush commented Nov 10, 2013

Marking this as close due to the previous commit and comment. We can reopen if you disagree.

@rbrush rbrush closed this as completed Nov 10, 2013
@mrrodriguez
Copy link
Collaborator

I noticed that you used retract! and insert-unconditional! in your example for :no-loop above.

Is there any particular reason for this? I'm just curious if logical insert would behave as expected in a rule like:

(defrule inserts-one-temp
  {:no-loop true}
  (not [Temperature (> temperature 0)])
  =>
  (insert! (->Temperature 100 "MCI")))

I have tested this and it seems to work like I'd expect. I have came across a use-case like this a few times and I'm making sure that this would be reliable.

@rbrush
Copy link
Contributor

rbrush commented Nov 21, 2013

No special reason, just logic specific to the use case I was showing in the example. I did that because I only wanted one temperature setting in the working memory (hence the retract!), and I used insert-unconditional! since it inserts a new fact that doesn't depend on the rule doing the insert to be continue to be true.

@mrrodriguez
Copy link
Collaborator

That makes sense. Thanks for the explanation. I was glad to see that the logical insert was not retracted in my example above. In Drools this would have prevented a loop, but it would retract the logical insertion, which I do not think is very intuitive.

Clara++

@rbrush
Copy link
Contributor

rbrush commented Nov 29, 2013

I updated the issue description to reflect the change made and assigned it to the 0.3.0 milestone in preparation for release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants