zeroSteiner / rule-engine

A lightweight, optionally typed expression language with a custom grammar for matching arbitrary Python objects.
https://zerosteiner.github.io/rule-engine/
BSD 3-Clause "New" or "Revised" License
445 stars 54 forks source link

forward chaining #6

Closed ehrenb closed 4 years ago

ehrenb commented 4 years ago

Hey, thanks for creating and sharing this library! Are there any plans in the future to implement forward chaining or backward chaining?

zeroSteiner commented 4 years ago

I'm not entirely sure what that is. Can you please clarify with an example?

ehrenb commented 4 years ago

I think these two links do a pretty good job explaining what they are:

http://pyke.sourceforge.net/logic_programming/rules/forward_chaining.html http://pyke.sourceforge.net/logic_programming/rules/backward_chaining.html

Pyke is a bit obsolete and no longer maintained, so I've been looking around for modern rule engines that support these features. durables-rules is another engine, but I've found the syntax to be a bit clunky and limited.

zeroSteiner commented 4 years ago

No, that's not really something that I had ever considered. If I get back to working on this project, the highest priority for me is definitely index support for lists / hashes.

In the mean time, you might be able to do something like this:

import rule_engine

def son_of(name, father=None):
    person = {'name': name}
    if father is not None:
        person['father'] = father
    return person

person = son_of('david', son_of('bruce', son_of('thomas', son_of('frederick', son_of('hiram')))))

# use the ternary operator in each to cause the evaluation to yield the father object on match
lineage = [
  'father.name == "bruce"     ? father : null',
  'father.name == "thomas"    ? father : null',
  'father.name == "frederick" ? father : null'
]

def forward_chaining(rules, root_thing):
    prev_thing = root_thing
    thing = root_thing
    for rule in rules:
        prev_thing = thing
        thing = rule_engine.Rule(rule).evaluate(thing)
        if thing is None:
            return False
    return prev_thing

forward_chaining(lineage, person)

I don't really like that solution because the Python implementation of the forward_chaining function relies on the rules properly evaluating to a father object meaning that whoever writes the rules would need to comply with that. I always intended to allow the end user to write the rules with nothing more than knowledge of the appropriate syntax. If the developer is always writing the rules though, this might work out just fine with a touch of error handling. It should demonstrate the gist of updating the thing to point to the next object. Now in this case the data is all structured ahead of time, but if you used a custom resolver you could for example query a database or some other thing when necessary.

I hope that helps.

zeroSteiner commented 4 years ago

I'm going to close this out, if you'd like to discuss it further just comment on the issue again and I'd be happy to reopen it.