6. Making Your Own Rules
6.1. Introducing defrules
Now that we've learned how to populate Jess's working memory, we can answer the obvious question: what is it good for? The answer is that defquerys can search it to find relationships between facts, and defrules can take actions based on the contents of one or more facts. A Jess rule is something like an if... then statement in a procedural language, but it is not used in a procedural way. While if... then statements are executed at a specific time and in a specific order, according to how the programmer writes them, Jess rules are executed whenever their if parts (their left-hand-sides or LHSs) are satisfied, given only that the rule engine is running. This makes Jess rules less deterministic than a typical procedural program. See the chapter on the Rete algorithm for an explanation of why this architecture can be many orders of magnitude faster than an equivalent set of traditional if... then statements. In this chapter we're going to make a lot of use of a "person" template that looks like this:Jess> (deftemplate person (slot firstName) (slot lastName) (slot age))
Jess> (defrule welcome-toddlers "Give a special greeting to young children" (person {age < 3}) => (printout t "Hello, little one!" crlf))
The LHS of a rule (the "if" part) consists of patterns that match facts, NOT function calls. The actions of a rule (the "then" clause) are made up of function calls. The following rule does NOT work:Our example rule, then, will be activated when an appropriate (person) fact appears in the working memory. When the rule executes, or fires, a message is printed. Let's turn this rule into a complete program. The function watch all tells Jess to print some useful diagnostics as we enter our program.This rule will NOT fire just because the function call (eq 1 1) would evaluate to true. Instead, Jess will try to find a fact in the working memory that looks like (eq 1 1). Unless you have previously asserted such a fact, this rule will NOT be activated and will not fire. If you want to fire a rule based on the evaluation of a function that is not related to a pattern, you can use the test CE.Jess> (defrule wrong-rule (eq 1 1) => (printout t "Just as I thought, 1 == 1!" crlf))
Jess> (deftemplate person (slot firstName) (slot lastName) (slot age))
TRUE
Jess> (watch all)
TRUE
Jess> (reset)
==> f-0 (MAIN::initial-fact) TRUE
Jess> (defrule welcome-toddlers "Give a special greeting to young children" (person {age < 3}) => (printout t "Hello, little one!" crlf))
welcome-toddlers: +1+1+1+t TRUE
Jess> (assert (person (age 2)))
==> f-1 (MAIN::person (firstName nil) (lastName nil) (age 2)) ==> Activation: MAIN::welcome-toddlers : f-1 <Fact-1>
Jess> (run)
FIRE 1 MAIN::welcome-toddlers f-1 Hello, little one! <== Focus MAIN 1
6.2. Simple patterns
A pattern is always a set of parentheses including the name of the fact to be matched plus zero or more slot descriptions. There are now two kinds of slot descriptions in Jess: the new-style "simplified" or "Java" syntax, and the old-style, more complex but more powerful syntax. New-style slot descriptions are enclosed in curly braces, like the one in our welcome-toddlers rule:
Jess> (defrule welcome-toddlers "Give a special greeting to young children" (person {age < 3}) => ((System.out) println "Hello, little one!"))
Old-style slot descriptions use parentheses instead of curly braces. The syntax allowed for the two kinds of descriptions are different. We'll talk mostly about the new simplified syntax in this section, and save most of the old-style syntax for the next section. However, there is one very easy and very important thing you can do with the old-style syntax that we'll need right away: you can declare a variable to refer to the contents of a slot. For example, look at the following pattern:
(person (age ?a) (firstName ?f) (lastName ?l))
This pattern will match any person fact. When the rule fires, Jess will assign the contents of the "age" slot to a variable "?a", the firstName slot to a variable "?f", and the lastName slot to "?l". You'll be able to use these variables elsewhere in the same rule, both on the left-hand side and on the right-hand side.
The simplified slot descriptions we'll talk about in this section are Java-like Boolean expressions (infix expressions) using the following operators:
- < (less than)
- <= (less than or equal to)
- > (greater than)
- >= (greater than or equal to)
- == (equals)
- != (not equal to)
- <> (not equal to, alternate syntax)
- && (and)
- || (or)
These operators are used in infix format, like Java operators. Within a simplified pattern, a symbol stands for the value of the slot in that pattern with the same name. The syntax is simple and a few examples will serve to document it. The first example matches any person who is between the ages of 13 and 19, inclusive:
Jess> (defrule teenager ?p <- (person {age > 12 && age < 20} (firstName ?name)) => (printout t ?name " is " ?p.age " years old." crlf))
The variable "?p" is a pattern binding; it's bound to the whole fact that matches this pattern. Note how we use the dotted variable syntax to get the the value of the "age" slot. We declared a variable for the firstName slot in an old-style slot description.
You can write tests that look at several slots at once:
Jess> (defrule same-first-and-last-name (person {firstName == lastName}) => (printout t "That is a funny name!" crlf))
You can use parentheses to group operations in Java patterns; for example:
Jess> (defrule teenage-or-bob (person {(age > 12 && age < 20) || firstName == Bob}) => (printout t "The person is a teenager, or is named 'Bob'." crlf))
6.2.1. Using multiple simple patterns together
Most rules have more than one pattern -- often many more. What's more, patterns relate to one another. For example, we might want to find two unrelated people who are the same age. To do this sort of task, you simply match one person, then match a second person and compare them to the first. To compare the two facts, we need to bind variables to them so we can refer to them; then we use a special "dot notation" to refer to the slots of the first fact:
Jess> (defrule two-same-age-different-name ?person1 <- (person) ?person2 <- (person {age == person1.age &&lastName != person1.lastName}) =>(printout t "Found two different " ?person1.age "-year-old people." crlf))
The variable "?person1" is another pattern binding. Note that when you refer to a pattern binding in a Java pattern, the "?" variable indicator is omitted.
6.3. Patterns in Depth
The curly-brace notation we looked at in the previous section is a simplified way of writing patterns that fills many basic needs. But Jess actually supports a richer syntax that gives you more capabilities. One limitation of the curly-brace notation is that it can only be used with unordered facts. It is this richer syntax that we'll cover here. Whereas the simplified slot patterns use curly braces, the richer syntax uses parentheses to enclose slots.
As previously shown, you can specify a variable name for a field in any of a rule's patterns (but not the pattern's head). A variable matches any value in that position within a rule. For example, the rule:
Jess> (deftemplate coordinate (slot x) (slot y))
Jess> (defrule example-2 (coordinate (x ?x) (y ?y)) => (printout t "Saw 'coordinate " ?x " " ?y "'" crlf))
A slot descriptor can also include any number of tests to qualify what it will match. Tests follow the variable name and are separated from it and from each other by an and (&) or or (|) symbol. (The variable name itself is actually optional.) Tests can be:
- A literal value (in which case the variable matches only that value); for example, the value 1.0 in (coordinate (x 1.0)).
- A variable which was assigned earlier on the rule's LHS. This will constrain the field to contain the same value as the variable was first bound to; for example, (coordinate (x ?x) (y ?x)) will only match coordinates facts with equal x and y values.
- A colon (:) followed by a function call, in which case the test succeeds if the function returns the special value TRUE. These are called predicate constraints; for example, (cordinate (x ?x&:(> ?x 10))) matches "coordinate" facts with x greater 10. There is a powerful shortcut way to write many predicate constraints which we'll look at in a minute.
-
An equals sign (=) followed by a function call. In this case the
field must match the return value of the function call. These are called
return value constraints. Note that both predicate constraints and
return-value constraints can refer to variables bound elsewhere in this
or any preceding pattern in the same rule. Note: pretty-printing
a rule containing a return value contstraint will show that it has been
transformed into an equivalent predicate constraint. An example of a
return-value constraint would be
(coordinate (x ?x) (y =(+ ?x 1)))
- A Java regular expression surrounded by "/" characters. The field must match the given regular expression (regular expressions are available only when you're using Jess under JDK 1.4 and up.) For example, the pattern (person (name /A.*/) matches people whose first initial is "A".
- Any of the other options preceded by a tilde (~), in which case the sense of the test is reversed (inequality or false); for example (coordinate (x ?x) (y ~?x)) matches coordinates in which x and y differ.
(foo ?X&:(oddp ?X)&:(< ?X 100)|0)
Jess> (defrule example-3 (not-b-and-c ?n1&~b ?n2&~c) (different ?d1 ~?d1) (same ?s ?s) (more-than-one-hundred ?m&:(> ?m 100)) (red-or-blue red|blue) => (printout t "Found what I wanted!" crlf))
The first pattern will match an ordered fact with head not-b-and-c with exactly two fields such that the first is not b and the second is not c. The second pattern will match any fact with head different and two fields such that the two fields have different values. The third pattern will match a fact with head same and two fields with identical values. The fourth pattern matches a fact with head more-than-one-hundred and a single field with a numeric value greater than 100. The last pattern matches a fact with head red-or-blue followed by either the symbol red or the symbol blue.
If you match to a defglobal with a pattern like (foo ?*x*), the match will only consider the value of the defglobal when the fact is asserted. Subsequent changes to the defglobal's value will not invalidate the match - i.e., the match does not reflect the current value of the defglobal, but only the value at the time the matching fact was asserted.
6.4. Matching in Multislots
Pattern matching in multislots (and in ordered facts, which are really just facts with a single multislot whose name is hidden) is similar to matching in regular slots. The main difference is that you may include separate clusters of tests for each field within a multislot. The number of clusters implicitly specifies the number of items in a matching multislot. So, for example, the grocery-list pattern in the following rule matches only grocery lists with exactly three items:
Jess> (defrule match-three-items (grocery-list ? ? ?) => (printout t "Found a three-item list" crlf))
TRUE
Jess> (assert (grocery-list eggs milk bacon))
<Fact-0>
Jess> (run)
Found a three-item list 1
Note that, as shown here, you can match a field without binding it to a named variable by omitting the variable name and using just a question mark (?) as a placeholder.
You can match any number (zero or more) of fields in a multislot or ordered fact using a multifield. A multifield is just a variable constraint preceded by a '$' character. The matched items are used to construct a list, and the list is assigned to that variable:
Jess> (defrule match-whole-list (grocery-list $?list) => (printout t "I need to buy " ?list crlf))
TRUE
Jess> (assert (grocery-list eggs milk bacon))
<Fact-0>
Jess> (run)
I need to buy (eggs milk bacon) 1
Multifields can be used in combination with other kinds of tests, and they're a very convenient way of saying "... and some other stuff." For example, this rule matches grocery lists containing bacon in any position. It does this by using two blank multifields: one to match all the items before bacon in the list, and the other (which in this case, will match zero items) to match all the items after.
Jess> (defrule match-list-with-bacon (grocery-list $? bacon $?) => (printout t "Yes, bacon is on the list" crlf))
TRUE
Jess> (assert (grocery-list eggs milk bacon))
<Fact-0>
Jess> (run)
Yes, bacon is on the list 1
Finally, note that a multifield is not a special kind of variable. When a multifield $?list is matched, it's the variable ?list that receives the value.
6.5. Pattern bindings
Sometimes you need a handle to an actual fact that helped to activate a rule. For example, when the rule fires, you may need to retract or modify the fact. To do this, you use a pattern-binding variable:Jess> (defrule example-5 ?fact <- (a "retract me") => (retract ?fact))
Jess> (defrule example-5-1 ?fact <- (initial-fact) => (printout t (call ?fact getName) crlf))
TRUE
Jess> (reset)
TRUE
Jess> (run)
initial-fact 1
Note that once a fact is asserted, Jess will always use the same jess.Fact object to represent it, even if the original fact is modified. Therefore, you can store references to fact objects in the slots of other facts as a way of representing structured data.
6.6. More about regular expressions
Jess's new regular expression facility builds on Java's java.util.regex package. This section presents a few examples of how it works.Jess> (defrule rule-1 (foo /xy+z/) =>) (defrule rule-2 (foo a ?x&/d*ef/) (bar ?x) =>)
The first rule matches a "foo" fact with a single field of the form "xyz", "xyyz", "xyyyz"... The second rule matches a "foo" fact with two fields: the symbol "a" followed by a string or symbol of the form "ef", "def", "ddef"...; this string is bound to the variable ?x and matched to the only field in the second pattern. Patterns are always matched against the entire contents of the field. You can write /.*abc.* to match for an embedded string "abc".
There is also a function regexp which can be used in procedural code. In this release, it just takes two arguments, a regular expression and a target string, and returns a boolean result.
6.7. Salience and conflict resolution
Each rule has a property called salience that is a kind of rule priority. Activated rules of the highest salience will fire first, followed by rules of lower salience. To force certain rules to always fire first or last, rules can include a salience declaration:Jess> (defrule example-6 (declare (salience -100)) (command exit-when-idle) => (printout t "exiting..." crlf))
6.8. The 'and' conditional element.
Any number of patterns can be enclosed in a list with and as the head. The resulting pattern is matched if and only if all of the enclosed patterns are matched. By themselves, and groups aren't very interesting, but combined with or and not conditional elements, they can be used to construct complex logical conditions. The entire left hand side of every rule and query is implicitly enclosed in an and conditional element.6.9. The 'or' conditional element.
Any number of patterns can be enclosed in a list with or as the head. The resulting pattern is matched if one or more of the patterns inside the or are matched. If more than one of the subpatterns are matched, the or is matched more than once:Jess> (defrule or-example-1 (or (a) (b) (c)) =>)
Jess> (assert (a) (b) (c))
Jess> (printout t (run) crlf)
3
Jess> (defrule or-example-2a (and (or (a) (b)) (c)) =>)
Jess> (defrule or-example-2b (or (and (a) (c)) (and (b) (c))) =>)
(not (or (x) (y))) => (and (not (x)) (not (y)))
6.10. The 'not' conditional element.
Any single pattern can be enclosed in a list with not as the head. In this case, the pattern is considered to match if a fact (or set of facts) which matches the pattern is not found. For example:Jess> (defrule example-7 (person ?x) (not (married ?x)) => (printout t ?x " is not married!" crlf))
Jess> (defrule no-odd-numbers (not (number ?n&:(oddp ?n))) => (printout t "There are no odd numbers." crlf))
Jess> (defrule forall-example (not (and (a ?x) (not (b ?x)))) =>)
6.11. The 'exists' conditional element.
A pattern can be enclosed in a list with exists as the head. An exists CE is true if there exist any facts that match the pattern, and false otherwise. exists is useful when you want a rule to fire only once, although there may be many facts that could potentially activate it.Jess> (defrule exists-demo (exists (honest ?)) => (printout t "There is at least one honest man!" crlf))
exists may not be combined in the same pattern with a test CE.
Note that exists is precisely equivalent to (and in fact, is implemented as) two nested not CEs; i.e., (exists (A)) is the same as (not (not (A))). It is rather common for people to write something like "(not (exists (A)))," but this is just a very inefficient way to write (not (A)).
6.12. The 'test' conditional element.
A pattern with test as the head is special; the body consists not of a pattern to match against the working memory but of a Boolean function. The result of evaluating this function determines whether the pattern matches. A test pattern fails if and only if the function evaluates to the symbol FALSE; if it evaluates to TRUE or any other value, the pattern with "match." For example:Jess> (deftemplate person (slot age))
Jess> (defrule example-8 (test (eq 4 (+ 2 2))) => (printout t "2 + 2 is 4!" crlf))
(not (test (eq ?X 3)))
(test (neq ?X 3))
Jess> (defrule rule_1 (foo ?X) (test (> ?X 3)) =>)
Jess> (defrule rule_2 (foo ?X&:(> ?X 3)) =>)
In fact, the functions in a test CE are simply added to the previous pattern's tests. Therefore these are not only equivalent, but they are in fact identical in every respect. You can use test CEs wherever you'd like without worrying about performance implications. When you need to evaluate a complicated function during pattern matching, a test CE is often clearer than the equivalent slot test.
You should now understand why, for rules in which a test CE is the first pattern on the LHS or the first pattern in a branch of an or CE, the pattern (initial-fact) is inserted to become the "preceding pattern" for the test. The fact (initial-fact) is therefore also important for the proper functioning of the test conditional element; the caution about reset in the preceding section applies equally to test.
6.12.1. Time-varying method returns
One useful property of the test CE is that it's the only valid place to put tests whose results might change without the contents of any slot changing. For example, imagine that you've got two Java classes, A and B, and that A has a method contains which takes a B as an argument and returns boolean. Further, imagine that for any given B object, the return value of contains will change over time. Finally, imagine that you've defined shadow fact templates for both these classes and are writing rules to work with them. Under these circumstances, a set of patterns like this:(A (OBJECT ?a)) (B (OBJECT ?b&:(?a contains ?b)))
(A (OBJECT ?a)) (B (OBJECT ?b)) (test (?a contains ?b))
6.12.2. When should I use test?
The test conditional element can be used whenever writing a test directly in a slot would be unclear. It is also useful with time-varying return values as described previously. There is no longer any performance penalty associated with the test conditional element.
6.13. The 'logical' conditional element.
The logical conditional element lets you specify logical dependencies among facts. All the facts asserted on the RHS of a rule become dependent on the matches to the logical patterns on that rule's LHS. If any of the matches later become invalid, the dependent facts are retracted automatically. In this simple example, a single fact is made to depend on another single fact:Jess> (defrule rule-1 (logical (faucet-open)) => (assert (water-flowing)))
TRUE
Jess> (assert (faucet-open))
<Fact-0>
Jess> (run)
1
Jess> (facts)
f-0 (MAIN::faucet-open) f-1 (MAIN::water-flowing) For a total of 2 facts in module MAIN.
Jess> (watch facts)
TRUE
Jess> (retract (fact-id 0))
<== f-0 (MAIN::faucet-open) <== f-1 (MAIN::water-flowing) TRUE
The Jess language functions dependents and dependencies let you query the logical dependencies among facts.
6.14. The 'forall' conditional element.
The "forall" grouping CE matches if, for every match of the first pattern inside it, all the subsequent patterns match. An example:Jess> (defrule every-employee-has-a-stapler-and-holepunch (forall (employee (name ?n)) (stapler (owner ?n)) (holepunch (owner ?n))) => (printout t "Every employee has a stapler and a holepunch." crlf))
This rule fires if there are a hundred employees and everyone owns the appropriate supplies. If a single employee doesn't own the supplies, the rule won't fire.
6.15. The 'accumulate' conditional element.
The "accumulate" CE is complicated, and perhaps hard to understand, but it's incredibly powerful. It lets you count facts, add up fields, store data into collections, etc. I will eventually need to write quite a bit of documentation for it, but for now, the following should get you started.The accumulate CE looks like this:
(accumulate <initializer> <action> <result> <conditional element>)
When an accumulate CE is encountered during matching (i.e., when the preceding pattern is matched, or when the contained CE is matched), the following steps occur:
- A new execution context is created.
- The initializer is executed in that context.
- If the CE is activated via the left input, all the matching tokens from the right memory are considered. If it's activated via the right input, each of the matching left tokens are visited. As each is visited, all of its matching right tokens are considered in turn.
- For each token considered, the variables it defines are bound in the execution context, and the action is executed.
- If a pattern binding is present, the result is bound to the given variable.
- Finally, the accumulate CE matches successfully and matching continues at the next conditional element.
What this all means is that "accumulate" lets you execute some code for every match, and returns the accumulated result. For example, this rule counts the number of employees making more than $100,000 per year. A variable is initialized to zero, and incremented for every match; that variable is then bound to the pattern binding.
Jess> (deftemplate employee (slot salary) (slot name))
Jess> (defrule count-highly-paid-employees ?c <- (accumulate (bind ?count 0) ;; initializer (bind ?count (+ ?count 1)) ;; action ?count ;; result (employee (salary ?s&:(> ?s 100000)))) ;; CE => (printout t ?c " employees make more than $100000/year." crlf))
This variation prints a list of those employees instead by storing all the names in an ArrayList:
Jess> (defrule count-highly-paid-employees ?c <- (accumulate (bind ?list (new java.util.ArrayList)) ;; initializer (?list add ?name) ;; action ?list ;; result (employee (name ?name) (salary ?s&:(> ?s 100000)))) ;; CE => (printout t (?c toString) crlf))
Warning: note that because matching one fact can cause accumulate to iterate over a large number of other facts, it can be computationally expensive. Do think about what you're doing when you use it.
Finally, note that accumulate is non-reentrant. You cannot nest one accumulate CE inside another, directly or indirectly.
6.16. The 'unique' conditional element.
The unique CE has been removed. The parser will accept but ignore it.6.17. Node index hash value.
The node index hash value is a tunable performance-related parameter that can be set globally or on a per-rule basis. A small value will save memory, possibly at the expense of performance; a larger value will use more memory but lead to faster rule LHS execution. In general, you might want to declare a large value for a rule that was likely to generate many partial matches (prime numbers are the best choices:)Jess> (defrule nihv-demo (declare (node-index-hash 169)) (item ?a) (item ?b) (item ?c) (item ?d) =>)
6.18. The 'slot-specific' declaration for deftemplates
Deftemplate definitions can now include a "declare" section just as defrules can. There are several different properties that can be declared. One is "slot-specific". A template with this declaration will be matched in a special way: if a fact, created from such a template, which matches the left-hand-side of a rule is modified, the result depends on whether the modified slot is named in the pattern used to match the fact. As an example, consider the following:Jess> (deftemplate D (declare (slot-specific TRUE)) (slot A) (slot B))
Jess> (defrule R ?d <- (D (A 1)) => (modify ?d (B 3)))
Without the "slot-specific" declaration, this rule would fire twice, because it modifies a fact matched on the LHS in such a way that the modified fact will still match (the rule fires only twice because the second firing will not modify the fact again: the modified slot value is a constant). With the declaration, it can simply fire once. This behavior is actually what many new users expect as the default; the technical term for it is refraction.
6.19. The 'no-loop' declaration for rules
If a rule includes the declaration (declare (no-loop TRUE)), then nothing that a rule does while firing can cause the immediate reactivation of the same rule; i.e., if a no-loop rule matches a fact, and the rule modifies that same fact such that the fact still matches, the rule will not be put back on the agenda, avoiding an infinite loop. This is basically just a stronger form of "slot-specific."6.20. Removing rules
You can undefine a rule with the jess.Rete.removeDefrule(String) method. This will remove the rule completely from the engine.
6.21. Forward and backward chaining
The rules we've seen so far have been forward-chaining rules, which basically means that the rules are treated as if... then statements, with the engine passively executing the RHSs of activated rules. Some rule-based systems, notable Prolog and its derivatives, support backward chaining. In a backwards chaining system, rules are still if... then statements, but the engine seeks steps to activate rules whose preconditions are not met. This behaviour is often called "goal seeking". Jess supports both forward and backward chaining. Note that the explanation of backward chaining in Jess is necessarily simplified here since full explanation requires a good understanding of the underlying algorithms used by Jess. To use backward chaining in Jess, you must first declare that certain fact templates will be backward chaining reactive. You can do this when you define the template:Jess> (deftemplate factorial (declare (ordered TRUE) (backchain-reactive TRUE))
Jess> (do-backward-chaining factorial)
Jess> (defrule print-factorial-10 (factorial 10 ?r1) => (printout t "The factorial of 10 is " ?r1 crlf))
(need-factorial 10 nil)
Jess> (defrule do-factorial (need-factorial ?x ?) => (bind ?r 1) (bind ?n ?x) (while (> ?n 1) (bind ?r (* ?r ?n)) (bind ?n (- ?n 1))) (assert (factorial ?x ?r)))
Jess> (do-backward-chaining foo)
TRUE
Jess> (do-backward-chaining bar)
TRUE
Jess> (defrule rule-1 (foo ?A ?B) => (printout t foo crlf))
TRUE
Jess> (defrule create-foo (need-foo $?) (bar ?X ?Y) => (assert (foo A B)))
TRUE
Jess> (defrule create-bar (need-bar $?) => (assert (bar C D)))
TRUE
Jess> (reset)
TRUE
Jess> (run)
foo 3
6.22. Defmodules
A typical rule-based system can easily include hundreds of rules, and a large one can contain many thousands. Developing such a complex system can be a difficult task, and preventing such a multitude of rules from interfering with one another can be hard too. You might hope to mitigate the problem by partitioning a rule base into manageable chunks. Modules let you divide rules and templates into distinct groups. The commands for listing constructs let you specify the name of a module, and can then operate on one module at a time. If you don't explicitly specify a module, these commands (and others) operate by default on the current module. If you don't explicitly define any modules, the current module is always the main module, which is named MAIN. All the constructs you've seen so far have been defined in MAIN, and therefore are often preceded by "MAIN::" when displayed by Jess. Besides helping you to manage large numbers of rules, modules also provide a control mechanism: the rules in a module will fire only when that module has the focus, and only one module can be in focus at a time.Note for CLIPS users: Jess's defmodule construct is similar to the CLIPS construct by the same name, but it is not identical. The syntax and the name resolution mechanism are simplified. The focus mechanism is much the same.
6.22.1. Defining constructs in modules
You can define a new module using the defmodule construct:Jess> (defmodule WORK)
TRUE
Jess> (deftemplate WORK::job (slot salary))
TRUE
Jess> (list-deftemplates WORK)
WORK::job For a total of 1 deftemplates in module WORK.
Jess> (get-current-module)
MAIN
Jess> (defmodule COMMUTE)
TRUE
Jess> (get-current-module)
COMMUTE
Jess> (deftemplate bus (slot route-number))
TRUE
Jess> (defrule take-the-bus ?bus <- (bus (route-number 76)) (have-correct-change) => (get-on ?bus))
TRUE
Jess> (ppdefrule take-the-bus)
"(defrule COMMUTE::take-the-bus ?bus <- (bus (route-number 76)) (have-correct-change) => (get-on ?bus))"
6.22.2. Modules, scope, and name resolution
A module defines a namespace for templates and rules. This means that two different modules can each contain a rule with a given name without conflicting -- i.e., rules named MAIN::initialize and COMMUTE::initialize could be defined simultaneously and coexist in the same program. Similarly, the templates COMPUTER::bus and COMMUTE::bus could both be defined. Given this fact, there is the question of how Jess decides which template the definition of a rule or query is referring to. When Jess is compiling a rule or deffacts definition, it will look for templates in three places, in order:- If a pattern explicitly names a module, only that module is searched.
- If the pattern does not specify a module, then the module in which the rule is defined is searched first.
- If the template is not found in the rule's module, the module MAIN is searched last. Note that this makes the MAIN module a sort of global namespace for templates.
Jess> (assert (MAIN::mortgage-payment 2000))
<Fact-0>
Jess> (defmodule WORK)
TRUE
Jess> (deftemplate job (slot salary))
TRUE
Jess> (defmodule HOME)
TRUE
Jess> (deftemplate hobby (slot name) (slot income))
TRUE
Jess> (defrule WORK::quit-job (job (salary ?s)) (HOME::hobby (income ?i&:(> ?i (/ ?s 2)))) (mortgage-payment ?m&:(< ?m ?i)) => (call-boss) (quit-job))
TRUE
Jess> (ppdefrule WORK::quit-job)
"(defrule WORK::quit-job (job (salary ?s)) (HOME::hobby (income ?i&:(> ?i (/ ?s 2)))) (MAIN::mortgage-payment ?m&:(< ?m ?i)) => (call-boss) (quit-job))"
6.22.3. Module focus and execution control
In the previous sections I described how modules provide a kind of namespace facility, allowing you to partition a rulebase into manageable chunks. Modules can also be used to control execution. In general, although any Jess rule can be activated at any time, only rules in the focus module will fire. Note that the focus module is independent from the current module discussed above. Initially, the module MAIN has the focus:Jess> (defmodule DRIVING)
TRUE
Jess> (defrule get-in-car => (printout t "Ready to go!" crlf))
TRUE
Jess> (reset)
TRUE
Jess> (run)
0
Jess> (focus DRIVING)
MAIN
Jess> (run)
Ready to go! 1
6.22.3.1. The auto-focus declaration
You can declare that a rule has the auto-focus property:Jess> (defmodule PROBLEMS)
TRUE
Jess> (defrule crash (declare (auto-focus TRUE)) (DRIVING::me ?location) (DRIVING::other-car ?location) => (printout t "Crash!" crlf) (halt))
TRUE
Jess> (defrule DRIVING::travel ?me <- (me ?location) => (printout t ".") (retract ?me) (assert (me (+ ?location 1))))
TRUE
Jess> (assert (me 1))
<Fact-1>
Jess> (assert (other-car 4))
<Fact-2>
Jess> (focus DRIVING)
MAIN
Jess> (run)
...Crash! 4
6.22.3.2. Returning from a rule RHS
If the function return is called from a rule's right-hand-side, it immediately terminates the execution of that rule's RHS. Furthermore, the current focus module is popped from the focus stack.This suggests that you can call a module like a subroutine. You call the module from a rule's RHS using focus and you return from the call using return.
To stop executing a rule's actions without popping the focus stack, use break instead.
Finally, note that the auto-focus declaration can be applied to defmodules too; an auto-focus module is equivalent to a regular module in which every rule has the auto-focus property.
6.22.4. Removing modules
This should not be a common requirement, but you can remove a module from the engine using the jess.Rete.removeDefmodule(String) method. You might want to do this during interactive development or experimentation. You won't be able to remove a module if it's in use. If there are any templates or rules defined in the module, you'll get an exception.