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

Using the output of an action in a generated fact #85

Open
williamkeller opened this issue Nov 30, 2022 · 4 comments
Open

Using the output of an action in a generated fact #85

williamkeller opened this issue Nov 30, 2022 · 4 comments

Comments

@williamkeller
Copy link

The docs say, about action blocks in productions, "Avoid modifying the engine inside action blocks (i.e., asserting and retracting facts and installing new rules). There are safer ways of doing this. Action blocks should only be used for side effects."

When a production fires, I want to write a record to a database, and then use the generated primary key of that record to assert a new fact for downstream processing. What is the best way to do that? Does the warning about modifying the engine apply to action objects as well? What is the safer way of doing this?

@williamkeller
Copy link
Author

What I'm presently doing is running everything through the engine with just facts, and then have a synchronization process afterwards that queries the engine and generates new database records for anything that it detects that's missing. But that's a clunky process and it limits the complexity of rule sets.

@ulfurinn
Copy link
Owner

ulfurinn commented Nov 30, 2022

Hard to say without seeing the code.

The biggest problem is that a production node may activate and deactivate several times before the engine reaches a stable state, depending on the ruleset, so changing persistent state from action blocks could have unintended effects. If you don't have negative matchers that may become invalidated, that might not be an issue for you.

But that aside, action blocks aren't really designed to have their output affect the engine state.

I'd continue your approach of running the engine on facts only and committing the state after it settles. Again, I can't really comment on the problems you're having with it without seeing the code; but have a look at the new entity iterator API in v0.4, I wrote it specifically to alleviate some of the pain of walking the knowledge base. It's still a bit verbose but I found it to be reasonably readable in practice.

Remember that you don't need external identity like database IDs to work with facts; you could generate placeholder objects (assign(:Ref) { Object.new }) to represent anonymous graph vertices and then make statements about those, and this also combines quite well with entity iterators. Any object that defines #== in a reasonable way can be used in a fact triple.

@vpereira
Copy link

vpereira commented May 9, 2023

Hi @ulfurinn, probably I have the same situation and i will try to explain it in a code:

engine = Wongi::Engine.create

my_ruleset = ruleset "Calculator" do
  c = Calculator.new
  rule 'multiply' do
    forall do
      has :A, :*, :B
    end
    make do
      action do |token|
        c.push(token[:A])
        c.push(token[:B])
        c.multiply
        puts "multiply #{c.to_s}"
      end
    end
  end
  ...
engine << my_ruleset

engine << [5, :*, 10]

it all works, and it will print the results as expected. I can do it for all operations, which is nice. However I wanted to go a step further, and instead of puts, I wanted to generate a new fact and match it in another rules, so i added the following code:

my_ruleset = ruleset "Calculator" do
  c = Calculator.new
  rule 'multiply' do
    forall do
      has :A, :*, :B
      assign :Results do |token|
        "multiply"
      end
    end
    make do
      results = "[0]"
      action do |token|
        c.push(token[:A])
        c.push(token[:B])
        c.multiply
        results = c.to_s
        puts "multiply #{c.to_s}"
      end
      gen :Results, :results, results.to_sym
    end
  end
 rule 'results' do
    forall do
      has :Results, :results, :Result
    end

    make do 
      action do |token|
        puts token.inspect
        puts "RESULT #{token[:Result]}"
      end
    end
  end
...

That when I run it, I get the follow results:

multiply [50]
TOKEN [ 520 ancestors=80.200 Results=multiply (subject of {"multiply" :results :"[0]"}) Result=[0] (object of {"multiply" :results :"[0]"}) ]
RESULT multiply

which means, the second rule "results" is being triggered, but the multiplication results isn't being carried on.. there is a way to accomplish what I'm trying to, or am I misusing the rules engine?

My expectation was, to get a fact like [:Results, :results, :Result] where token[:Result] in the rule "results" would then the result of the multiplication operation

Thanks!

@ulfurinn
Copy link
Owner

ulfurinn commented May 9, 2023

The direct content of forall and make blocks is evaluated only once when the rule is compiled, so gen :Results, :results, results.to_sym captures the current state of results and that's the only time it looks at it.

The assign clause should be used if you want dynamically calculated variables.

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