What about Methods in Rules?
This column is the next in a series that provides the reader with best practices on using or choosing a rules engine. The target audience for this series is typically the user of a rule engine, i.e., a programmer or someone with programming skills. All coding examples should be read as pseudo-code and should be easily translated to a specific target syntax for a rule engine that supports backward and forward chaining in an object-oriented environment.
We will discuss recommendations on the question of when are method-calls useful in combination with logic expressed as declarative rules. In this description the following concepts are important:
- Rule: a programming construct that is used to describe the relationship between attributes in your domain model
A rule can be a statement, which has to be true at all times, but it can also be a description of how an attribute value can be derived from other attributes' values.
- Method: a program construct that can execute an action or calculate a value
The value can be returned as return value of the function or by using output parameters. The method can also have input parameters.
- Function: a synonym for 'method'
Not all inference engines can cope with methods (functions) in rules. A question that needs to be answered when working with functions is whether the function that is 'called' from within a rule — as well as the functions called within these functions — is effectively considered to be part of the rule. If so, it is very impressive and powerful but I'd like you to consider the implications for declarative rules.
If a function is called from within the condition of a rule, this function may perform various tasks (even start a new inference process) and therefore may cause all kinds of side-effects that affect the current chaining process. Calling functions from within a rule is a very dangerous thing!
So, are we not allowed to use functions in rules? The answer is that you need to be very careful with functions. In general it is not a good idea to mix the declarative logic used in rules with procedural logic. It can cause side-effects that not only affect the reasoning process but also makes it harder to re-use rules.
That said, two kinds of methods are commonly used in rules:
- Accessor methods (e.g., the get- and set-methods of attributes) can be used without risk, as long as they have no side-effects.
- Methods that perform a calculation can be shared by several rules. You may want to write reusable code for these calculations.
There is a test to find out whether a function is allowed to be used in a rule:
If the function-body can be substituted in the rule text in a straightforward way — without creating an invalid rule (syntax error) — the method is said to be 'declarative' and can be used in a rule without risk.
Example applying the test
Consider this condition in a rule:
| example rule
if age() < 18
If function Age( ) is implemented as follows:
| code snippet
| code snippet
return(currentdate - birthdate)
the substitution in the condition would result in:
| example accessor method substitution in rule
if age < 18
| example calculation method substitution in rule
if currentdate - birthdate < 18
But what if Age( ) is implemented like this:
| code snippet
birthdate = getagefromdatabase(id)
if birthdate = null
Besides the fact that it is impossible to substitute this function body into the rule, it also has a side effect: the 'birthdate' attribute is assigned a value.
So, you see that if you adapt this heuristic, you will use functions only for expressions that you want to reuse in rules.
Now, if you really have the need for procedural logic during the inference — for instance to get data from the database (or user) on a when-needed basis, or to perform immediate processing when the action of a certain rule is executed — read my next column. Otherwise, please skip it.
# # #