oakes / odoyle-rules

A rules engine for Clojure(Script)
The Unlicense
530 stars 20 forks source link

Truth Maintenance? #9

Open allentiak opened 3 years ago

allentiak commented 3 years ago

Just curious...

Have you considered supporting Truth Maintenance? Why?/Why not?

oakes commented 3 years ago

I thought about it but i need a really specific use case to implement anything, and so far i haven't needed it. I am also really hoping to keep the codebase small. I find that a lot of uses of truth maintenance in clara are really just to get around its inability to directly update facts. There are certainly uses beyond that though...

allentiak commented 3 years ago

so far i haven't needed it [...] I find that a lot of uses of truth maintenance in clara are really just to get around its inability to directly update facts.

Ah, I see. Maybe this rationale could be added to the README, then?

There are certainly uses beyond that though...

Could you please point me to where could I get such examples? I'm just starting with Rules Engines...

oakes commented 3 years ago

Yeah if i can think of a good way of explaining it i'll add it. My last sentence was just a hedge because i am not certain what those use cases would be -- but i don't want to assume they don't exist. Let's take the example on clara's website: https://www.clara-rules.org/docs/truthmaint/

Their rule makes you a "PremierCustomer" if you have more than 2000 purchases. But, if after your 2000th order you return an item, it will automatically retract that fact so you won't be a premier customer anymore.

In o'doyle you could do it naively like this:

::premier-customer
[:what
 [person-id ::total-purchases amount]
 :then
 (o/insert! person-id ::premier-customer? (> amount 2000))]

This will always update the ::premier-customer? boolean when the ::total-purchases fact is updated. However, it will cause unnecessary work since you're constantly updating the fact when its value hasn't changed. You could avoid that by checking the existing value like this:

::premier-customer
[:what
 [person-id ::total-purchases amount]
 [person-id ::premier-customer? premier? {:then false}]
 :then
 (let [new-premier? (> amount 2000)]
   (when (not= premier? new-premier?)
     (o/insert! person-id ::premier-customer? new-premier?)))]

If you do it this way, you'll need to make sure you insert an initial ::premier-customer? fact for each customer, or else the rule will never get a chance to fire.

allentiak commented 3 years ago

Thanks for the explanation, @oakes !

wandersoncferreira commented 3 years ago

Hello @oakes , very nice library and presentation as usual. I have been watching other presentations about your text editors and very amazed by all the ideas :)

I implemented a more complete example from Clara Rules to talk about Truth Maintenance (TM) and I would like some code review https://github.com/wandersoncferreira/odoyle-example-truth-maintainance/blob/main/src/odoyle/truth.clj this is my first contact with odoyle and I am still wrapping my head around it.

The comparison basically shows what TM guarantees to users:

  1. a new fact (F1) triggers rule A
  2. rule A inserts new fact (F2) in the database
  3. fact F1 is retracted 4. O'Doyle does not retract F2, Clara Rules does.

Maybe I am missing something.

I relied on this feature some years ago while modeling some financial contracts that had additional entities created depending on contract state e.g.

  1. At 1PM the contract owner sent a partial payment, then I created two new entities RemainderValue, AdditionalFees
  2. At 2PM the contract owner sent the rest of the money, then the two previous entities got removed and a new ContractPaid created

The second step happened automatically because the status of the contract that triggered the creation of those two previous entities now has changed.

Is that a way to replicate this behavior? Thanks!

oakes commented 3 years ago

I took a look at your code. Firstly, instead of making the ::insert-cold-temperature rule insert new facts, why not just use it as a query directly? In other words, instead of (o/query-all with-retracted-session ::cold) you can just do (o/query-all with-retracted-session ::insert-cold-temperature) and it will return what you want. You can rename it to something like ::cold-temperatures and get rid of the :then block.

It looks like (o/query-all with-retracted-session ::records-facts) works fine, which makes sense, because ultimately that data is coming from a :then-finally which does run after retractions. So any derived facts that are created that way should update correctly after a fact is retracted.

wandersoncferreira commented 3 years ago

why not just use it as a query directly?

Nice! That was it! I updated other parts of the example to ask queries directly and it produces the same output as Clara :)

jtrunick commented 1 year ago

I have some experience with Clara as well and the Truth Maintenance was useful, but I'm looking at O'Doyle because for my use case I think TM will actually get in the way.

I'm planning on triggering threads to fetch facts (in RHS) and then update the session with facts like in "agent style" (serialize the responses to the session). I'm guessing this won't be a problem (@oakes please let me know otherwise) with O'Doyle, but doing this in Clara would be problematic. I think the core issue is that there isn't a way to insert facts into Clara and say "oh yeah, btw this is a result of a rule that fired earlier". So, if TM was added to O'Doyle it would be nice if the API could expect "out-of-band" RHSs.