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
Binding to values derived from previous variables #142
Comments
I think we should support this...or at least I can't think of any reason not to that would make sense to an end user. It isn't a typical join/test scenario but I don't want to force users to be aware of that distinction. I think the key pieces here are going to be:
The trickiest algorithmic part is probably going to be sorting out the order we need to place the nodes so all of the bound variables are visible. But this is something we should solve anyway as described in issue #133. I'll poke around this a bit more tomorrow. |
Looking at this a bit more closely, I think the best approach will be to refactor the extract-tests type of functionality into a specialized join node that runs arbitrary tests and can create new bindings. So we might rename the current JoinNode to "HashJoinNode" and create an "ExpressionJoinNode" that can run arbitrary logic on the beta side of the network. HashJoinNode is more efficient, but a new ExpressionJoinNode will be used when needed to handle arbitrary expressions and relationships. I think this simplifies things in the long run, since we will no longer need to artificially transform our network into join and test nodes, and it also simplifies ordering of the nodes as well. This change won't be trivial, but it does drop some existing complexity and ensures our nodes can express any expression you could put in a rule. |
I think that approach makes sense to me when going the direction of allowing bindings to be introduced in test nodes. I agree it'd likely be simpler to reason about with less transformations. Thanks for looking into this and taking the time to post your design thoughts out here along the way. |
The above commit should fix this (at least, the examples included in the bug report now work and are included in a unit test.) Most of the work done was in the refactoring for issue #133. The change in the above commit simply reuses the same binding logic used for fact matching to run arbitrary cross-fact expressions. I'll plan on merging this to master soon unless some flaw presents itself. Mike, please let me know if you run into any problems with this logic. |
Thanks! I am working on testing this out and seeing how it works out. |
I ran this against my existing codebase and it looks good. It caught one misused bindings in a condition, but I don't consider that a bad thing. There were some odd cases before where a condition was able to refer to a binding in one constraint that it introduced in a previous constraint. I think it was just related to the test extraction before, but I never dug into it. It was confusing that it worked then though and I generally advised against it. This looks like quite a bit of a change. Thanks for getting to it so fast! |
Merged into master! I'm going to go ahead and close this now, but we can create new issues if anything related pops up. |
The use-case for this one is a little difficult to explain, but let me try.
I'll start with some motivating examples where I think the resulting error is at least confusing.
Example (1) This first example compiles, but not into something meaningful.
The issue here lies in the extracted TestNode that is generated to try and deal with the non-hash-based join condition.
This TestNode fn looks something like [1]:
Where
?__gen__28586
is bound totemperature
of the:original-condition
.This doesn't make sense since
?t
doesn't have a binding - it just comes out nil.Example (2) This doesn't compile into a Rete tree, but the error reported may be less-than-ideal.
To the caller, it looks like
?x
should be resolvable.Given these examples, I can see how the immediate response would be that it doesn't make sense to set up a join binding like this since it isn't really a "join".
This usage of a variable binding is more to create what I'd consider a "local binding" so that you can reuse some computation elsewhere in the rule.
Example (1) can be extended to demonstrate this idea (although there is no reason to do this particular example this way) :
The result of this is not intuitive from my perspective and I've seen other be confused by it.
Overall, I think this may be using the join bindings for an unintended use. If that is the case, I'd vote in favor of putting some guards against these things in the form of throwing meaningful exceptions during Rete compilation.
On the other hand, there are times when it is "convenient" to store a derived calculation made in a LHS condition as a variable binding so that it can be tested in other subsequent conditions and used in the RHS. It could aid performance to not repeat the same calculation in the LHS and RHS for example.
This works (and doesn't make much sense, but keeping it short):
This can probably be abused as well as an aid to writing poorly designed rules.
I have been able to track down the internals in clara.rules.compiler that contribute to this issue. I am just not sure on the direction we want to take with usages like this.
[1] Separate issue I'd probably like to think about: I had to add to the TestNode structure on the Rete tree in order to visually see what a TestNode condition looks like prior to eval. I think it would aid in debugging and visualizing the Rete tree if the un-eval'ed form was preserved on a field of the TestNode. Same goes for other join filter fn's etc.
The text was updated successfully, but these errors were encountered: