I'm using an extremely informal notation to describe syntax. Basically strings in <angle-brackets> are some kind of data that must be supplied; things in [square brackets] are optional, things ending with + can appear one or more times, and things ending with * can appear zero or more times.
In general, input to Jess is free-format. Newlines are generally not significant and are treated as whitespace; exceptions will be noted.
Jess atoms are case sensitive: foo, FOO and Foo are all different atoms.
The best atoms consist of letters, numbers, underscores, and dashes; dashes are traditional word separators. The following are all valid atoms:
foo first-value contestant#1 _abcThere are three "magic" atoms that Jess interprets specially: nil, which is somewhat akin to Java's null value; and TRUE and FALSE, which are Jess' boolean values.
3 4. 5.643 6.0E4 1D
"foo" "Hello, World" "\"Nonsense,\" he said firmly." "Hello, There"The last string is equivalent to the Java string "Hello,\nThere".
(+ 3 2) (a b c) ("Hello, World") () (deftemplate foo (slot bar))The first element of a list (the car of the list in LISP parlance) is often called the list's head in Jess.
; This is a list (a b c)Comments can appear anywhere in a Jess program.
Function calls in Jess are simply lists. Function calls use a prefix notation; a list whose head is an atom that is the name of an existing function can be a function call. For example, an expression that uses the + function to add the numbers 2 and 3 would be written (+ 2 3). When evaluated, the value of this expression is the number 5 (not a list containing the single element 5!). In general, expressions are recognized as such and evaluated in context when appropriate. You can type expressions at the Jess> prompt. Jess evaluates the expression and prints the result:
Jess> (+ 2 3) 5 Jess> (+ (+ 2 3) (* 3 3)) 14Note that you can nest function calls; the outer function is responsible for evaluating the inner function calls.
Jess comes with a large number of built-in functions that do everything from math, program control and string manipulations, to giving you access to Java APIs.
One of the most commonly used functions is printout. printout is used to send text to Jess's standard output, or to a file. A complete explanation will have to wait, but for now, all you need to know is contained in the following example:
Jess> (printout t "The answer is " 42 "!" crlf) The answer is 42!Another useful function is batch. batch evaluates a file of Jess code. To run the Jess source file examples/hello.clp you can enter
Jess> (batch examples/hello.clp) Hello, world!
Each of these functions (along with all the others) is described more thoroughly in the Jess function guide.
Jess> (bind ?x "The value") "The value"Multifields are generally created using special multifield functions like create$ and can then be bound to multivariables:
Jess> (bind $?grocery-list (create$ eggs bread milk)) (eggs bread milk)Variables need not (and cannot) be declared before their first use (except for special variables called defglobals).
Note that to see the value of a variable at the Jess> prompt, you can simply type the variable's name.
Jess> (bind ?a 123) 123 Jess> ?a 123
(defglobal [?<global-name> = <value>]+)Global variable names must begin and end with an asterisk. Valid global variable names look like
?*a* ?*all-values* ?*counter*When a global variable is created, it is initialized to the given value. When the reset command is subsequently issued, the variable may be reset to this same value, depending on the current setting of the reset-globals property. There is a function named set-reset-globals that you can use to set this property. An example will help.
Jess> (defglobal ?*x* = 3) TRUE Jess> ?*x* 3 Jess> (bind ?*x* 4) 4 Jess> ?*x* 4 Jess> (reset) TRUE Jess> ?*x* 3 Jess> (bind ?*x* 4) 4 Jess> (set-reset-globals nil) FALSE Jess> (reset) TRUE Jess> ?*x* 4You can read about the set-reset-globals and the accompanying get-reset-globals function in the Jess function guide.
(deffunction <function-name> [<doc-comment>] (<parameter>*) <expr>* [<return-specifier>])The <function-name> must be an atom. Each <parameter> must be a variable name. The optional <doc-comment> is a double-quoted string that can describe the purpose of the function. There may be an arbitrary number of <expr> expressions. The optional <return-specifier> gives the return value of the function. It can either be an explicit use of the return function or it can be any value or expression. Control flow in deffunctions is achieved via control-flow functions like foreach, if, and while. The following is a deffunction that returns the larger of its two numeric arguments:
Jess> (deffunction max (?a ?b) (if (> ?a ?b) then (return ?a) else (return ?b))) TRUENote that this could have also been written as:
Jess> (deffunction max (?a ?b) (if (> ?a ?b) then ?a else ?b)) TRUEThis function can now be called anywhere a Jess function call can be used. For example
Jess> (printout t "The greater of 3 and 5 is " (max 3 5) "." crlf) The greater of 3 and 5 is 5.Normally a deffunction takes a specific number of arguments. To write a deffunction that takes an arbitrary number of arguments, make the last formal parameter be a multifield variable. When the deffunction is called, this multifield will contain all the remaining arguments passed to the function. A deffunction can accept no more than one such wildcard argument, and it must be the last argument to the function.
Here are some examples of what defadvice looks like.
This intercepts calls to 'plus' (+) and adds the extra argument '1', such that (+ 2 2) becomes (+ 2 2 1) -> 5. The variable '$?argv' is special. It always refers to the list of arguments the real Jess function will receive when it is called.
Jess> (defadvice before + (bind $?argv (create$ $?argv 1))) TRUE Jess> (+ 2 2) 5This makes all additions equal to 1. By returning, the defadvice keeps the real function from ever being called.
Jess> (defadvice before + (return 1)) TRUE Jess> (+ 2 2) 1This subtracts one from the return value of the + function. ?retval is another magic variable - it's the value the real function returned. When we're done, we remove the advice with undefadvice.
Jess> (defadvice after + (return (- ?retval 1))) TRUE Jess> (+ 2 2) 3 Jess> (undefadvice +) Jess> (+ 2 2) 4
Jess> (bind ?ht (new java.util.Hashtable)) <External-Address:java.util.Hashtable> Jess> (call ?ht put "key1" "element1") Jess> (call ?ht put "key2" "element2") Jess> (call ?ht get "key1") "element1"As you can see, Jess converts freely between Java and Jess types when it can. Java objects that can't be represented as a Jess type are called external address values. The Hashtable in the example above is one of these.
Jess can also access member variables of Java objects using the set-member and get-member functions.
Jess> (bind ?pt (new java.awt.Point)) <External-Address:java.awt.Point> Jess> (set-member ?pt x 37) 37 Jess> (set-member ?pt y 42) 42 Jess> (get-member ?pt x) 37You can access static members by using the name of the class instead of an object as the first argument to these functions.
Jess> (get-member System out) <External-Address:java.io.PrintStream>Note that we don't have to say "java.lang.System." The java.lang package is implicitly "imported" much as it is in Java code. Jess also has an import function that you can use explicitly.
Jess converts values from Java to Jess types according to the following table.
Java type | Jess type |
---|---|
A null reference | The atom 'nil' |
A void return value | The atom 'nil' |
String | RU.STRING |
An array | A Jess multifield |
boolean or java.lang.Boolean | The atoms 'TRUE' and 'FALSE' |
byte, short, int, or their wrappers | RU.INTEGER |
long or Long | RU.LONG |
double, float or their wrappers | RU.FLOAT |
char or java.lang.Character | RU.ATOM |
anything else | RU.EXTERNAL_ADDRESS |
Jess converts values from Jess to Java types with some flexibility, according to this table. Generally when converting in this direction, Jess has some idea of a target type; i.e., Jess has a java.lang.Class object and a jess.Value object, and wants to turn the Value's contents into something assignable to the type named by the Class. Hence the atom 'TRUE' could be passed to a function expecting a boolean argument, or to one expecting a String argument, and the call would succeed in both cases.
Jess type | Possible Java types |
---|---|
RU.EXTERNAL_ADDRESS | The wrapped object |
The atom 'nil' | A null reference |
The atoms 'TRUE' or 'FALSE' | java.lang.Boolean or boolean |
RU.ATOM, RU.STRING | String, char, java.lang.Character |
RU.FLOAT | float, double, and their wrappers |
RU.INTEGER | long, short, int, byte, char, and their wrappers |
RU.LONG | long, short, int, byte, char, and their wrappers |
RU.LIST | A Java array |
Sometimes you might have trouble calling overloaded methods - for example, passing the String "TRUE" to a Java method that is overloaded to take either a boolean or a String. In this case, you can always resort to using an explicit wrapper class - in this case, passing a java.lang.Boolean object should fix the problem.
To learn more about the syntax of call, new, set-member, get-member, and other Java integration functions, see the Jess function guide.
(shopping-list eggs milk bread) (person "Bob Smith" Male 35) (father-of danielle ejfried)You can add ordered facts to the knowledge base using the assert function. You can see a list of all the facts in the knowledge base using the facts command. You can completely clear Jess of all facts and other data using the clear command.
Jess> (reset) TRUE Jess> (assert (father-of danielle ejfried)) <Fact-1> Jess> (facts) f-0 (MAIN::initial-fact) f-1 (MAIN::father-of danielle ejfried) For a total of 2 facts.As you can see, each fact is assigned an integer index (the fact-id) when it is asserted. You can remove an individual fact from the knowledge base using the retract function.
Jess> (retract (fact-id 1)) TRUE Jess> (facts) f-0 (MAIN::initial-fact) For a total of 1 facts.The fact (initial-fact) is asserted by the reset command. It is used internally by Jess to keep track of its own operations; you should generally not retract it.
(person (name "Bob Smith") (age 34) (gender Male)) (automobile (make Ford) (model Explorer) (year 1999))before you can create unordered facts, you have to define the slots they have using the deftemplate construct:
(deftemplate <deftemplate-name> [extends <classname>] [<doc-comment>] [(slot <slot-name> [(default | default-dynamic <value>)] [(type <typespec>))]*)The <deftemplate-name> is the head of the facts that will be created using this template. There may be an arbitrary number of slots. Each <slot-name> must be an atom. The default slot qualifier states that the default value of a slot in a new fact is given by <value>; the default is the atom nil. The 'default-dynamic' version will evaluate the given value each time a new fact using this template is asserted. The 'type' slot qualifier is accepted but not currently enforced by Jess; it specifies what data type the slot is allowed to hold. Acceptable values are ANY, INTEGER, FLOAT, NUMBER, ATOM, STRING, LEXEME, and OBJECT.
As an example, defining the following template:
Jess> (deftemplate automobile "A specific car." (slot make) (slot model) (slot year (type INTEGER)) (slot color (default white)))would allow you to define facts like this:
Jess> (assert (automobile (make Chrysler) (model LeBaron) (year 1997))) <Fact-0> Jess> (facts) f-0 (MAIN::automobile (make Chrysler) (model LeBaron) (year 1997) (color white)) For a total of 1 facts.Note that the car is white by default. If you don't supply a default value for a slot, and then don't supply a value when a fact is asserted, the special value nil is used. Also note that any number of additional automobiles could also be simultaneously asserted onto the fact list using this template.
A given slot in a deftemplate fact can normally hold only one value. If you want a slot that can hold multiple values, use the multislot keyword instead:
Jess> (deftemplate box (slot location) (multislot contents)) TRUE Jess> (bind ?id (assert (box (location kitchen) (contents spatula sponge frying-pan)))) <Fact-1>(We're saving the fact-id returned by (assert) in the variable ?id, for use below.) A multislot has the default value () (the empty list) if no other default is specified.
You can change the values in the slots of an unordered fact using the modify command. Building on the immediately preceding example, we can move the box into the dining room:
Jess> (modify ?id (location dining-room)) <Fact-1> Jess> (facts) f-0 (MAIN::automobile (make Chrysler) (model LeBaron) (year 1997) (color white)) f-1 (MAIN::box (location dining-room) (contents spatula sponge frying-pan)) For a total of 2 facts.
The optional extends clause of the deftemplate construct lets you define one template in terms of another. For example, you could define a used-auto as a kind of automobile with more data:
Jess> (deftemplate used-auto extends automobile (slot mileage) (slot blue-book-value) (multislot owners)) TRUEA used-auto fact would now have all the slots of an automobile, plus three more. As we'll see later, this inheritance relationship will let you act on all automobiles (used or not) when you so desire, or only on the used ones.
Note that an ordered fact is very similar to an unordered fact with only one multislot. The similarity is so strong, that in fact this is how ordered facts are implemented in Jess. If you assert an unordered fact, Jess automatically generates a template for it. This generated template will contain a single slot named "__data". Jess treats these facts specially - the name of the slot is normally hidden when the facts are displayed. This is really just a syntactic shorthand, though; ordered facts really are just unordered facts with a single multislot named "__data".
Jess> (deffacts my-facts "The documentation string" (foo bar) (box (location garage) (contents scissors paper rock)) (used-auto (year 1992) (make Saturn) (model SL1) (mileage 120000) (blue-book-value 3500) (owners ejfried))) TRUE Jess> (reset) TRUE Jess> (facts) f-0 (MAIN::initial-fact) f-1 (MAIN::foo bar) f-2 (MAIN::box (location garage) (contents scissors paper rock)) f-3 (MAIN::used-auto (make Saturn) (model SL1) (year 1992) (color white) (mileage 120000) (blue-book-value 3500) (owners ejfried)) For a total of 4 facts.Note that we can specify the slots of an unordered fact in any order (hence the name.) Jess rearranges our inputs into a canonical order so that they're always the same.
An example will probably help at this point. Let's say you have the following Java Bean class
import java.io.Serializable; public class ExampleBean implements Serializable { private String m_name = "Bob"; public String getName() { return m_name; } public void setName(String s) { m_name = s; } } C:\> java ExampleBeanThis Bean has one property called "name". Before we can insert any of these Beans onto the knowledge base, we need a template to represent them: we must use defclass to tell Jess to generate it:
Jess> (defclass simple ExampleBean) ExampleBean Jess> (ppdeftemplate simple) "(deftemplate MAIN::simple extends MAIN::__fact \"$JAVA-OBJECT$ ExampleBean\" (slot class (default <External-Address:jess.SerializablePD>)) (slot name (default <External-Address:jess.SerializablePD>)) (slot OBJECT (type 2048)))"This is a strange looking template, but it does have a slot called "name", as we'd expect, that arises from the "name" property of our Bean. The slot "class" comes from the method getClass() that every object inherits from java.lang.Object, while the slot OBJECT is added by Jess; its value is always a reference to the Bean itself. See how the first argument to defclass is used as the template name.
Note that if you want your Java Beans to work with Jess's bload and bsave commands, the individual classes need to implement the java.io.Serializable tag interface.Now let's say we want an actual ExampleBean in our knowledge base. Here we'll create one from Jess code, but it could come from anywhere. We will use the definstance function to add the object to the knowledge base.
Jess> (bind ?sb (new ExampleBean)) <External-Address:ExampleBean> Jess> (definstance simple ?sb static) <Fact-0> Jess> (facts) f-0 (MAIN::simple (class <External-Address:java.lang.Class>) (name "Bob") (OBJECT <External-Address:ExampleBean>)) For a total of 1 facts.As soon as we issue the definstance command, a fact representing the Bean appears in the knowledge base.
Now watch what happens if we change the "name" property of our Bean.
Jess> (call ?sb setName "Fred") Jess> (facts) f-0 (MAIN::simple (class <External-Address:java.lang.Class>) (name "Bob") (OBJECT <External-Address:ExampleBean>)) For a total of 1 facts.Hmmm. The knowledge base still thinks our Bean's name is "Bob", even though we changed it to "Fred". What happens if we issue a reset command?
Jess> (reset) TRUE Jess> (facts) f-0 (MAIN::initial-fact) f-1 (MAIN::simple (class <External-Address:java.lang.Class>) (name "Fred") (OBJECT <External-Address:ExampleBean>)) For a total of 2 facts.reset updates the definstance facts in the knowledge base to match their Java Beans. This behaviour is what you get when (as we did here) you specify static in the definstance command. Static definstances are refreshed only when a reset is issued.
If you want to have your definstance facts stay continuously up to date, Jess needs to be notified whenever a Bean property changes. For this to happen, the Bean has to support the use of java.beans.PropertyChangeListeners. For Beans that fulfil this requirement, you can specify dynamic in the definstance command, and the knowledge base will be updated every time a property of the Bean changes. Jess comes with some example Beans that can be used in this way; see, for example, the Jess60/jess/examples/simple directory.
defclasses, like deftemplates, can extend one another. In fact, deftemplates can extend defclasses, and defclasses can extend deftemplates. Of course, for a defclass to extend a deftemplate, the corresponding Bean class must have property names that match the template's slot names. Note, also, that just because two Java classes have an inheritance relationship doesn't mean that if both are defclassed the two defclasses will. You must explicitly declare all such relationships using extends. See the full documenation for defclass for details.
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.
Rules are defined in Jess using the defrule construct. A very simple rule looks like this:
Jess> (defrule do-change-baby "If baby is wet, change baby's diaper." (baby-is-wet) => (change-baby))This rule has two parts, separated by the "=>" symbol (which you can read as "then".) The first part consists of the LHS pattern (baby-is-wet). The second part consists of the RHS action (change-baby). Although it's hard to tell due to the LISP-like syntax, the LHS of a rule consists of patterns which are used to match facts in the knowledge base, while the RHS contains function calls.
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 the fact (baby-is-wet) appears in the knowledge base. When the rule executes, or fires, the function (change-baby) is called (presumably this function is defined elsewhere in our imaginary program.) 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.Jess> (defrule wrong-rule (eq 1 1) => (printout t "Just as I thought, 1 == 1!" crlf))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 on the knowledge base 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, you can use the test CE.
Jess> (watch all) TRUE Jess> (reset) ==> f-0 (MAIN::initial-fact) TRUE Jess> (deffunction change-baby () (printout t "Baby is now dry" crlf)) TRUE Jess> (defrule do-change-baby (baby-is-wet) => (change-baby)) do-change-baby: +1+1+1+t TRUE Jess> (assert (baby-is-wet)) ==> f-1 (MAIN::baby-is-wet) ==> Activation: MAIN::do-change-baby : f-1 <Fact-1>Some of these diagnostics are interesting. We see first of all how issuing the reset command asserts the fact (initial-fact). You should always issue a reset command when working with rules. When the rule itself is entered, we see the line "+1+1+t". This tells you something about how the rule is interpreted by Jess internally (see The Rete Algorithm for more information.) When the fact (baby-is-wet) is asserted, we see the diagnostic "Activation: MAIN::do-change-baby : f-1". This means that Jess has noticed that the rule do-change-baby has all of its LHS conditions met by the given list of facts ("f-1").
After all this, our rule didn't fire; why not? Jess rules only fire while the rule engine is running (although they can be activated while the engine is not running.) To start the engine running, we issue the run command.
Jess> (run) FIRE 1 MAIN::do-change-baby f-1 Baby is now dry <== Focus MAIN 1As soon as we enter the run command, the activated rule fires. Since we have watch all, Jess prints the diagnostic FIRE 1 do-change-baby f-1 to notify us of this. We then see the output of the rule's RHS actions. The final number "1" is the number of rules that fired (it is the return value of the run command.) The run function returns when there are no more activated rules to fire.
What would happen if we entered (run) again? Nothing. A rule will be activated only once for a given set of facts; once it has fired, that rule will not fire again for the same list of facts. We won't change the baby again until the (baby-is-wet) fact is retracted (perhaps by (reset) and asserted again. In fact, this rule should itself retract the (baby-is-wet) fact itself; to learn how, see the section on pattern bindings, below.
Rules are uniquely identified by their name. If a rule named my-rule exists, and you define another rule named my-rule, the first version is deleted and will not fire again, even if it was activated at the time the new version was defined.
Jess> (defrule example-2 (a ?x ?y) => (printout t "Saw 'a " ?x " " ?y "'" crlf))will be activated each time any fact with head a having two fields is asserted: (a b c), (a 1 2), (a a a), and so forth. As in the example, the variables thus matched in the patterns (or LHS) of a rule are available in the actions (RHS) of the same rule.
Each such variable field in a pattern 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 ampersands (&) or pipes (|). (The variable name itself is actually optional.) Tests can be:
Ampersands (&) represent logical "and", while pipes (|) represent logical "or." & has a higher precedence than |, so that the following
(foo ?X&:(oddp ?X)&:(< ?X 100)|0)matches a foo fact with a single field containing either an odd number less than 100, or 0.
Here's an example of a rule that uses several kinds of tests:
Jess> (defrule example-3 (not-b-and-c ?n1&~b ?n2&~c) (different ?d1 ?d2&~?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 a 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 atom red or the atom blue.
A few more details about patterns: you can match a field without binding it to a variable by omitting the variable name and using just a question mark (?) as a placeholder. You can match any number of fields in a multislot or unordered fact using a multivariable (one starting with $?):
Jess> (defrule example-4 (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
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.
Jess> (defrule example-5 ?fact <- (a "retract me") => (retract ?fact))The variable (?fact, in this case) is assigned the fact ID of the particular fact that activated the rule.
Note that ?fact is a jess.Value object of type RU.FACT, not an integer. It is basically a reference to a jess.Fact object. You can convert an ordinary number into a FACT using the fact-id function. You can convert a FACT into an integer when necessary by using reflection to call the Fact.getFactId() function. The jess.Value.externalAddressValue() method can be called on a FACT Value to obtain the actual jess.Fact object from Java code. In Jess code, a fact-id essentially is a jess.Fact, and you can call jess.Fact methods on a fact-id directly:
Jess> (defrule example-5-1 ?fact <- (initial-fact) => (printout t (call ?fact getName) crlf)) TRUE Jess> (reset) TRUE Jess> (run) initial-fact 1See the section on the jess.FactIDValue class for more information.
Note that pattern bindings can't currently be applied to patterns inside any of the grouping conditional elements described below; this will be remedied in a future release.
Jess> (defrule example-6 (declare (salience -100)) (command exit-when-idle) => (printout t "exiting..." crlf))Declaring a low salience value for a rule makes it fire after all other rules of higher salience. A high value makes a rule fire before all rules of lower salience. The default salience value is zero. Salience values can be integers, global variables, or function calls. See the set-salience-evaluation command for details about when such function calls will be evaluated.
The order in which multiple rules of the same salience are fired is determined by the active conflict resolution strategy. Jess comes with two strategies: "depth" (the default) and "breadth." In the "depth" strategy, the most recently activated rules will fire before others of the same salience. In the "breadth" strategy, rules fire in the order in which they are activated. In many situations, the difference does not matter, but for some problems the conflict resolution strategy is important. You can write your own strategies in Java; see the chapter on extending Jess with Java for details. You can set the current strategy with the set-strategy command.
Note that the use of salience is generally discouraged, for two reasons: first it is considered bad style in rule-based programming to try to force rules to fire in a particular order. Secondly, use of salience will have a negative impact on performance, at least with the built-in conflict resolution strategies.
You can see the list of activated, but not yet fired, rules with the agenda command.
The entire left hand side of every rule and query is implicitly enclosed in an and conditional element.
Jess> (defrule or-example-1 (or (a) (b) (c)) =>) Jess> (assert (a) (b) (c)) Jess> (printout t (run) crlf) 3An and group can be used inside of an or group, and vice versa. In the latter case, Jess will rearrange the patterns so that there is a single or at the top level. For example, the rule
(defrule or-example-2a (and (or (a) (b)) (c)) =>)will be automatically rearranged to
(defrule or-example-2b (or (and (a) (c)) (and (b) (c))) =>)Note that if the right hand side of a rule uses a variable defined by matching on the left hand side of that rule, and the variable is defined by one or more branches of an or pattern but not all branches, then a runtime error may occur.
Jess knows that the subrules created from a given rule are related. If a rule is removed (either using undefrule or implicitly by defining a new rule with the same name as an existing one) every subrule associated with that rule is undefined.
Regarding subrules and efficiency: remember that similar patterns are shared between rules in the Rete network. Therefore, splitting a rule into subrules does not mean that the amount of pattern-matching work is increased; much of the splitting may indeed be undone when the rules are compiled into the network.
On the other hand, keep the implementation in mind when you define your rules. If an or conditional element is the first pattern on a rule, all the subsequent pattern-matching on that rule's left-hand side won't be shared between the subrules, since sharing only occurs as far as two rules are similar reading from the top down. Placing or conditional elements near the end of a rule will lead to more sharing between the subrules.
Note: although subrules will probably always be part of the implementation of the or conditional element in Jess, it is very likely that they will no longer be user-visible at some time in the future.
Jess> (defrule example-7 (person ?x) (not (married ?x)) => (printout t ?x " is not married!" crlf))Note that a not pattern cannot define any variables that are used in subsequent patterns (since a not pattern does not match any facts, it cannot be used to define the values of any variables!) You can introduce variables in a not pattern, so long as they are used only within that pattern; i.e,
Jess> (defrule no-odd-numbers (not (number ?n&:(oddp ?n))) => (printout t "There are no odd numbers." crlf))Similarly, a not pattern can't have a pattern binding.
A not CE is evaluated only when either a fact matching it exists, or when the pattern immediately before the not on the rule's LHS is evaluated. If a not CE is the first pattern on a rule's LHS, or is the the first the pattern in an and group, or is the only pattern on a given branch of an or group, the pattern (initial-fact) is inserted to become this important preceding pattern. Therefore, the fact (initial-fact) created by the reset command is necessary to the proper functioning of some not patterns. For this reason, it is especially important to issue a reset command before attempting to run the rule engine when working with not patterns.
Multiple not CEs can be nested to produce some interesting effects (see the discussion of the exists CE).
The not CE can be used in arbitrary combination with the and and or CEs. You can define complex logical structures this way. For example, suppose you want a rule to fire once if for every fact (a ?x), there is a fact (b ?x). You could express that as
Jess> (defrule forall-example (not (and (a ?x) (not (b ?x)))) =>)i.e., "It is not true that for some ?x, there is an (a ?x) and no (b ?x)". You might recognize this as the CLIPS forall conditional element; a future version of Jess will include the forall shorthand.
The rearrangements are based on DeMorgan's rules for logical operations, which can be stated in Jess syntax like this:
1. (not (and (x) (y))) => (or (not (x)) (not (y))) 2. (not (or (x) (y))) => (and (not (x)) (not (y)))Jess transforms the left hand side of every rule to meet two constraints:
Jess> (deftemplate person (slot age)) Jess> (defrule example-8 (person (age ?x)) (test (> ?x 30)) => (printout t ?x " is over 30!" crlf))Short-circuit evaluation is used; i.e., if a function call evaluates to FALSE, no further functions are evaluated and the test CE fails immediately. Note that a test pattern, like a not, cannot contain any variables that are not bound before that pattern. test and not may be combined:
(not (test (eq ?X 3)))is equivalent to:
(test (neq ?X 3))
A test CE is evaluated every time the preceding pattern on the rule's LHS is evaluated. Therefore the following two rules are precisely equivalent in behaviour:
Jess> (defrule rule_1 (foo ?X) (test (> ?X 3)) =>) Jess> (defrule rule_2 (foo ?X&:(> ?X 3)) =>)
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.
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. Jess> (watch facts) TRUE Jess> (retract (fact-id 0)) <== f-0 (MAIN::faucet-open) <== f-1 (MAIN::water-flowing) TRUEThe (water-flowing) fact is logically dependent on the (faucet-open) fact, so when the latter is retracted, the former is removed, too.
A fact may receive logical support from multiple sources -- i.e., it may be asserted multiple times with a different set of logical supports each time. Such a fact isn't automatically retracted unless each of its logical supports is removed.
If a fact is asserted without explicit logical support, it is said to be unconditionally supported. If an unconditionally supported fact also receives explicit logical support, removing that support will not cause the fact to be retracted.
If one or more logical CEs appear in a rule, they must be the first patterns in that rule; i.e., a logical CE cannot be preceded in a rule by any other kind of CE.
Definstance facts are no different than other facts with regard to the logical CE. Definstance facts can provide logical support and can receive logical support. In the current implementation, definstance facts can only provide logical support as a whole. In a future version of Jess, it will be possible for a definstance fact to provide logical support based on any combination of individual slot values.
The logical CE can be used together with all the other CEs, including not and exists. A fact can thus be logically dependent on the non-existence of another fact, or on the existence of some category of facts in general.
Jess> (deftemplate tax-form (slot social-security-number)) Jess> (deftemplate person (slot social-security-number) (slot name)) Jess> (defrule unique-demo (tax-form (social-security-number ?num)) (unique (person (social-security-number ?num) (name ?name))) => (printout t "Auditing " ?name "..." crlf))Here the unique CE is providing a hint to Jess that only one person can have a given Social Security number. Given this knowledge, Jess knows that once it has found the person that matches a given tax form, it doesn't need to look any further. In practice, this can result in performance gains of 20-30% on real problems.
unique may not be combined in the same patten with either test or not CEs.
Prolog users may recognize that unique is quite similar to that language's ! (cut) operator.
Jess> (defrule exists-demo (exists (honest ?)) => (printout t "There is at least one honest man!" crlf))If there are any honest men in the world, the rule will fire once and only once.
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))).
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) =>)See the discussion of the set-node-index-hash function for a full discussion of this value and what it means.
To use backward chaining in Jess, you must first declare that certain fact templates will be backward chaining reactive using the do-backward-chaining function:
Jess> (do-backward-chaining factorial)If the template is unordered -- i.e., if it is explicitly defined with a (deftemplate) construct -- then it must be defined before calling do-backward-chaining. Then you can define rules which match such patterns. Note that do-backward-chaining must be called before defining any rules which use the template.
Jess> (defrule print-factorial-10 (factorial 10 ?r1) => (printout t "The factorial of 10 is " ?r1 crlf))When the rule compiler sees that a pattern matches a backward chaining reactive template, it rewrites the rule and inserts some special code into the internal representation of the rule's LHS. This code asserts a fact onto the fact-list that looks like
(need-factorial 10 nil)if, when the rule engine is reset, there are no matches for this pattern. The head of the fact is constructed by taking the head of the reactive pattern and adding the prefix "need-". Now, you can write rules which match these need-(x) facts.
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)))The rule compiler rewrites rules like this too: it adds a negated match for the factorial pattern itself to the rule's LHS.
The end result is that you can write rules which match on (factorial), and if they are close to firing except they need a (factorial) fact to do so, any (need-factorial) rules may be activated. If these rules fire, then the needed facts appear, and the (factorial)-matching rules fire. This, then, is backwards chaining! Jess will chain backwards through any number of reactive patterns. For example:
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 3In this example, none of the rules can be activated at first. Jess sees that rule-1 could be activated if there were an appropriate foo fact, so it generates the request (need-foo nil nil). This matches part of the LHS of rule create-foo cannot fire for want of a bar fact. Jess therefore creates a (need-bar nil nil) request. This matches the LHS of the rule create-bar,which fires and asserts (bar C D). This activates create-foo, which fires, asserts (foo A B), thereby activating rule-1, which then fires.
There is a special conditional element, (explicit), which you can wrap around a pattern to inhibit backwards chaining on an otherwise reactive pattern.
Jess> (defquery search "Finds foo facts with a specified first field" (declare (variables ?X)) (foo ?X ?Y))Then if the knowledge base contains these facts:
Jess> (deffacts data (foo blue red) (bar blue green) (foo blue pink) (foo red blue) (foo blue blue) (foo orange yellow) (bar blue purple))Then the following Jess code Will print the output shown:
Jess> (reset) Jess> (bind ?it (run-query search blue)) Jess> (while (?it hasNext) (bind ?token (call ?it next)) (bind ?fact (call ?token fact 1)) (bind ?slot (fact-slot-value ?fact __data)) (bind ?datum (nth$ 2 ?slot)) (printout t ?datum crlf)) red pink blue FALSEbecause these three values follow blue in a foo fact.
Let's break this code down to see what it's doing. As previously stated, (run-query) returns the query results as a java.util.Iterator. The Iterator interface has a method next() that you call to retrieve each individual result; it also has a hasNext() method which returns true as long as there are more results to return. That explains the (while (?it hasNext) ... (call ?it next)) structure.
Each individual result is a jess.Token object. A token is basically just a collection of jess.Fact objects; here it is a collection that matches this query. We call the fact() method of jess.Token to retrieve the individual facts within the Token. Note that each match begins with an extra fact - a __query-trigger fact that triggers the matching process, asserted by the run-query command; hence the argument to the call to Token.fact() above is 1, not 0.
Once we have the right fact, we're interested in the second item in the data part of the fact (the first item in the fact is the head and it's stored differently.) As stated above, the slot data for ordered facts is stored in a single multifield slot named __data. We retrieve the contents of that slot using the fact-slot-value function, then use the nth$ function to retrieve the second slot (nth$ uses a one-based index.)
The following Java code is similar to the Jess snippets above. It defines the same query and deffacts, runs the query and then collects the red, pink and blue values in a Vector as Strings.
import jess.*; import java.util.*; public class ExQuery { public static void main(String [] argv) throws JessException { // Create engine, define query and data Rete r = new Rete(); r.executeCommand("(defquery search (declare (variables ?X)) (foo ?X ?Y))"); r.executeCommand("(deffacts data" + "(foo blue red)" + "(bar blue green)" + "(foo blue pink)" + "(foo red blue)" + "(foo blue blue)" + "(foo orange yellow)" + "(bar blue purple))"); // Assert all the facts r.reset(); // Run the query, store the result r.store("RESULT", r.runQuery("search", new ValueVector().add(new Value("blue", RU.ATOM)))); r.executeCommand("(store RESULT (run-query search blue))"); // Fetch the result (an Iterator). Iterator e = (Iterator) r.fetch("RESULT").externalAddressValue(null); ArrayList v = new ArrayList(); // Pick each element of the Iterator apart and store the // interesting part in the ArrayList v. while (e.hasNext()) { Token t = (Token) e.next(); // We want the second fact in the token - the first is the query trigger Fact f = t.fact(1); // The first and only slot of this fact is the __data multislot. ValueVector multislot = f.get(0).listValue(null); // The second element of this slot is the datum we're interested in. v.add(multislot.get(1).stringValue(null)); } for (Iterator answers = v.iterator(); answers.hasNext();) System.out.println(answers.next()); } } C:\> java ExQuery red pink blue
Defqueries can use virtually all of the same features that rule LHSs can, except for salience. The function ppdefrule can also pretty-print queries.
(declare (variables ?X ?Y ...))which is quite similar to the syntax of a rule salience declaration.
Note that each token will contain one more fact than there are patterns on the query's LHS; this extra fact is used internally by Jess to execute the query.
You must supply exactly one value for each external variable of the named query.
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.
Jess> (defmodule WORK) TRUEYou can place a deftemplate, defrule, or deffacts into a specific module by qualifying the name of the construct with the module name:
Jess> (deftemplate WORK::job (slot salary)) TRUE Jess> (list-deftemplates WORK) WORK::job For a total of 1 deftemplates.Once you have defined a module, it becomes the current module:
Jess> (get-current-module) MAIN Jess> (defmodule COMMUTE) TRUE Jess> (get-current-module) COMMUTEIf you don't specify a module, all deffacts, templates and rules you define will automatically become part of the current module:
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 <- (COMMUTE::bus (route-number 76)) (COMMUTE::have-correct-change) => (get-on ?bus))"You can set the current module explicitly using the set-current-module function. Note that the implied template have-correct-change was created in the COMMUTE module, because that's where the rule was defined.
When Jess is compiling a rule or deffacts definition, it will look for templates in three places, in order:
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 (WORK::job (salary ?s)) (HOME::hobby (income ?i&:(> ?i (/ ?s 2)))) (MAIN::mortgage-payment ?m&:(< ?m ?i)) => (call-boss) (quit-job))"In this example, three deftemplates are defined in three different modules: MAIN::mortgage-payment, WORK::job, and HOME::hobby. Jess finds the WORK::job template because the rule is defined in the WORK module. It finds the HOME::hobby template because it is explicitly qualified with the module name. And the MAIN::mortgage-payment template is found because the MAIN module is always searched as a last resort if no module name is specified.
Commands which accept the name of a construct as an argument (like ppdefrule, ppdeffacts, etc) will search for the named construct in the same way as is described above.
Note that many of the commands that list constructs (facts, list-deftemplates, rules, etc) accept a module name or "*" as an optional argument. If no argument is specified, these commands operate only on the current module. If a module name is given, they operate on the named module. If "*" is given, they operate on all modules.
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) 0In the example above, the rule doesn't fire because the DRIVING module doesn't have the focus. You can move the focus to another module using the focus function (which returns the name of the previous focus module:)
Jess> (focus DRIVING) MAIN Jess> (run) Ready to go! 1Note that you can call focus from the right-hand-side of a rule to change the focus while the engine is running.
Jess actually maintains a focus stack containing an arbitrary number of modules. The focus module is, by definition, the module on top of the stack. When there are no more activated rules in the focus module, it is "popped" from the stack, and the next module underneath becomes the focus module. You also can manipulate the focus stack with the functions pop-focus, list-focus-stack, get-focus-stack, and clear-focus-stack.
The example program dilemma.clp shows a good use of modules for execution control.
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! 4When an auto-focus rule is activated, the module it appears in is automatically pushed onto the focus stack and becomes the focus module. Modules with auto-focus rules make great "background tasks."
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.