2.
The Jess Language
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.
2.1. Basics
2.1.1. Atoms
The atom or symbol is a core concept of the Jess language. Atoms are very
much like identifiers in other languages. A Jess atom can contain letters,
numbers, and the following punctuation: $*=+/<>_?#. . An atom
may not begin with a number; it may begin with some punctuation marks (some
have special meanings as operators when they appear at the start of an
atom).
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 _abc
There 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.
2.1.2. Numbers
Jess uses the Java functions
java.lang.Integer.parseInt
and
java.lang.Double.parseDouble
to parse integer and floating point numbers, respectively. See the
documentation for those methods for a precise syntax
description. The following are all valid numbers:
3 4. 5.643 6.0E4 1D
2.1.3. Strings
Character strings in Jess are denoted using double quotes
("). Backslashes (\) can be used to
escape embedded quote symbols. Note that Jess strings are unlike Java
strings in several important ways. First, no "escape sequences" are
recognized. You cannot embed a newline in a string using
"\n", for example. On the other hand, real newlines are
allowed inside double-quoted strings; they become part of the string.
The following are all valid strings:
"foo" "Hello, World" "\"Nonsense,\" he said firmly." "Hello,
There"
The last string is equivalent to the Java string "Hello,\nThere".
2.1.4. Lists
Another fundamental unit of syntax in Jess is the list. A list always consists
of an enclosing set of parentheses and zero or more atoms, numbers, strings,
or other lists. The following are valid lists:
(+ 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.
2.1.5. Comments
Programmer's comments in Jess begin with a semicolon (;) and extend
to the end of the line of text. Here is an example of a comment:
; This is a list
(a b c)
Comments can appear anywhere in a Jess program.
2.2. Functions
As in LISP, all code in Jess (control structures, assignments,
procedure calls) takes the form of a function call.
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))
14
Note 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.
2.3. Variables
Programming variables in Jess are atoms that begin with the question mark
(?) character. The question mark is part of the variable's name.
A normal variable can refer to a single atom, number, or string. A variable
whose first character is instead a $ (for example, $?X)
is a multivariable, which can refer to a special kind of list
called a multifield. You assign to any variable using the
bind function:
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
2.3.1. Global variables (or defglobals)
Any variables you create at the Jess> prompt, or at the "top level" of
any Jess language program, are cleared whenever the reset
command is issued. This makes them somewhat transient; they are fine
for scratch variables but are not persistent global variables in the
normal sense of the word. To create global variables that are not
destroyed by reset, you can use the defglobal
construct.
(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*
4
You can read about the set-reset-globals and the accompanying
get-reset-globals function in the Jess function guide.
2.4. Deffunctions
You can define your own functions using the deffunction
construct. A deffunction construct looks like this:
(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)))
TRUE
Note that this could have also been written as:
Jess> (deffunction max (?a ?b)
(if (> ?a ?b) then
?a
else
?b))
TRUE
This 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.
2.5. Defadvice
Sometimes a Jess function won't behave exactly as you'd like. The
defadvice construct lets you write some Jess code which will
be executed before or after each time a given Jess function is
called. defadvice lets you easily "wrap" extra code
around any Jess function, such that it executes before (and thus can
alter the argument list seen by the real function, or short-circuit it
completely by returning a value of its own) or after the real function
(and thus can see the return value of the real function and possibly
alter it. ) defadvice provides a great way for Jess add-on authors to
extend Jess without needing to change any internal code.
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)
5
This 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)
1
This 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
2.6. Java reflection
Among the list of functions above are a set that let you create and
manipulate Java objects directly from Jess. Using them, you can do
virtually anything you can do from Java code, except for defining new
classes. Here is an example in which I create a Java Hashtable
and add a few String objects to it, then lookup one object and display it.
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)
37
You 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.
2.7. The knowledge base
A rule-based system maintains a collection of knowledge nuggets called
facts. This collection is known as the knowledge base.
It is somewhat akin to a relational database, especially in that the
facts must have a specific structure. In Jess, there are three kinds
of facts: ordered facts, unordered facts, and
definstance facts.
2.7.1. Ordered facts
Ordered facts are simply lists, where the first field (the head
of the list) acts as a sort of category for the fact. Here are some
examples of ordered facts:
(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.
2.7.2. Unordered facts
Ordered facts are useful, but they are unstructured. Sometimes (most
of the time) you need a bit more organization. In object-oriented
languages, objects have named fields in which data
appears. Unordered facts offer this capability (although the fields are
traditionally called slots.)
(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))
TRUE
A 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 ordered 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".
2.7.3. The deffacts construct
Typing separate assert commands for each of many facts is
rather tedious. To make life easier in this regard, Jess includes the
deffacts construct. A deffacts construct is a simply
a named list of facts. The facts in all defined deffacts are
asserted into the knowledge base whenever a reset command is
issued:
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.
2.7.4. Definstance facts
You may have noticed that unordered facts look a bit like Java
objects, or specifically, like Java Beans. The similarity is that both
have a list of slots (for Java Beans, they're called
properties) which contains values that might change over
time. Jess has a mechanism for automatically generating templates
that represent specific types of Java Beans. Jess can then use these
templates to store a representation of a Java Bean's properties on
the knowledge base. The knowledge base representation of the Bean can
be static (changing infrequently, like a snapshot of the
properties at one point in time) or dynamic (changing automatically
whenever the Bean's properties change.) The Jess commands that make
this possible are defclass and definstance.
defclass tells Jess to generate a special
template to represent a category of Beans, while
definstance puts a representation of one specific Bean onto
the fact base.
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; }
}
This 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 fulfill 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
Jess61p4/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.
One final note about Java Beans used with Jess: Beans are often
operating in a multithreaded environment, and so it's
important to protect their data with synchronized blocks or
synchronized methods. However, sending
PropertyChangeEvents while holding a lock on the Bean
itself can be dangerous, as the Java Beans Specification
points out:
"In order to reduce the risk of deadlocks, we strongly recommend that
event sources should avoid holding their own internal locks when they
call event listener methods. Specifically, as in the example code in
Section 6.5.1, we recommend they should avoid using a synchronized
method to fire an event and should instead merely use a synchronized
block to locate the target listeners and then call the event listeners
from unsynchronized code." -- JavaBean Specification, v 1.0.1, p.31.
Failing to heed this advice can indeed cause deadlocks in Jess.
2.8. Defrules
Now that we've learned how to develop a knowledge base, we can answer
the obvious question: what is it good for? The answer is that
queries can search it to find
relationships between facts, and rules
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.
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:
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.
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> (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
1
As 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.
2.8.1. Basic Patterns
If all the patterns of a rule had to be given literally as above, Jess
would not be very powerful. However, patterns can also include wildcards
and various kinds of predicates (comparisons and boolean functions).
You can specify a variable name instead of a value 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> (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:
-
A literal value (in which case the variable matches only that
value); for example, the values b and c in (a b
c).
-
Another variable (which must have been matched earlier in the rule's LHS).
This will constrain the field to contain the same value as the variable
was first bound to; for example, (a ?X ?X) will only match "a"
facts followed by two equal 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, (a ?X&:(> ?X 10)
matches "a" facts with one field, a number greater than 10.
-
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 (a ?X =(+ ?X 1)), which matches "a"
facts with two fields, both numbers with the second number greater than
the first by one.
-
Any of the other options preceded by a tilde (~), in which case
the sense of the test is reversed (inequality or false); for example
(a ?X ~?X) matches "a" facts with two fields as long as the
two fields contains different values.
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.
2.8.2. 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))
The variable (?fact, in this case) is bound to 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.factValue() 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
1
See the section on the
jess.FactIDValue class for more information.
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.
2.8.3. 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))
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.
2.8.4. 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.
2.8.5. 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
An 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)))
=>)
DeMorgan's second rule of logical equivalence, namely
(not (or (x) (y))) => (and (not (x)) (not (y)))
will be used when necessary to hoist an or up to the top level.
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.
2.8.5.1. Subrule generation and the 'or' conditional element.
A rule containing an 'or' conditional element with n
branches is precisely equivalent to n rules, each of
which has one branch as its left hand side. In fact, this is
how the or conditional element is implemented: Jess
imternally generates one new rule for each branch. Each
of these generated rules is a subrule. For a rule
named rule-name, the first subrule is named
rule-name, the second is rule-name&1,
the third is rule-name&2, etc. Each of these
subrules is added to the Rete network individually. If you
execute the (rules) command, you will see each of them
listed separately. If you use the
ppdefrule function to see a
pretty-print representation of a subrule, you will see only
the representation of that rule. Note that since '&' is
a token delimiter in the Jess grammar, you can only refer to
a subrule with an ampersand in the name by placing the whole
name in quotes; i.e., (ppdefrule
"rule-name&6").
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.
2.8.6. 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))
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.
2.8.7. The 'test' conditional element.
A pattern with test as the head is special; the body consists
not of a pattern to match against the knowledge base but of one or
more boolean functions, which are evaluated in order. The results
determine whether the pattern matches. A test pattern fails if
and only if one of the functions evaluates to the atom FALSE;
if they all evaluate to TRUE or any other value, the pattern
with "match." For example:
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.
2.8.7.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 class, 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 defclasses 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)))
is incorrect. If the return value of contains changes, the
match will be invalidated and Jess's internal data structures
may be corrupted. In particular, this kind of construct tends
to cause memory leaks.
The correct way to express this same set of patterns is to use
the test conditional element, like this:
(A (OBJECT ?a))
(B (OBJECT ?b))
(test (?a contains ?b))
The function contains is now guaranteed to be called at most
once for each combination of target and argument, and so
any variation in return value will have no impact.
2.8.8. 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 logicalpatterns 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.
Jess> (watch facts)
TRUE
Jess> (retract (fact-id 0))
<== f-0 (MAIN::faucet-open)
<== f-1 (MAIN::water-flowing)
TRUE
The (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.
2.8.9. The 'unique' conditional element.
The unique CE has been removed. The parser will accept but ignore it.
2.8.10. 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))
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))).
2.8.11. 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)
=>)
See the discussion of the
set-node-index-hash
function for a full discussion of this value and what it means.
2.8.12. 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 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
3
In 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.
2.9. Defqueries
The defquery construct lets you create a special kind of rule
with no right-hand-side. While rules act spontaneously,
queries are used to search the knowledge base under direct program
control. A rule is activated once for each matching set of facts,
while a query gives you a java.util.Iterator of all the
matches. An example should make this clear. Suppose we have defined
this query:
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
FALSE
because 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. You can use the node-index-hash declaration,
just as for rules.
2.9.1. The variable declaration
You might have already realized that two different kinds of variables
can appear in a query: those that are "internal" to the query, like ?Y
in the query above, and those that are "external", or to be specified in the
run-query command when the query is executed. Jess assumes
all variables in a query are internal by default; you must declare any
external variables explicitly using the syntax
(declare (variables ?X ?Y ...))
which is quite similar to the syntax of a rule salience declaration.
2.9.2. The max-background-rules declaration
It can be convenient to use queries as triggers for backward chaining.
For this to be useful, Rete.run() must be called while the query
is being evaluated, to allow the backward chaining to occur. Facts generated
by rules fired during this run may appear as part of the query results. (If this
makes no sense whatsoever to you, don't worry about it; just skip over
this section for now.)
By default, no rules will fire while a query is being executed. If you want
to allow backward chaining to occur in response to a query, you can use the
max-background-rules declaration -- i.e.,
(declare (max-background-rules 10))
would allow a maximum of 10 rules to fire while this particular query was being
executed.
2.9.3. The run-query command
The run-query command
lets you supply values for the external variables of a query and
obtain a list of matches. This function returns a
java.util.Iterator of
jess.Token object, one for
each matching combination of facts. The example code above calls
fact(0) on each jess.Token, to get the first jess.Fact
object from the jess.Token, then calls get(0) on the
fact to get the data from the first slot (which for ordered facts, is
a multislot named __data; see the
documentation for jess.Fact) and then uses (nth$ 2)to get the
second entry in that multislot.
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.
2.9.4. The count-query-results command
To obtain just the number of matches for a query, you can use the
count-query-results
function. This function accepts the same arguments as
run-query, but returns an integer, the
number of matches.
2.9.5. The future of queries
defquery is a new feature, and the syntax may change; in
particular, a simpler mechanism for obtaining query results may be
defined. Suggestions are welcome.
2.10. 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.
2.10.1. Defining constructs in modules
You can define a new module using the defmodule
construct:
Jess> (defmodule WORK)
TRUE
You 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)
COMMUTE
If 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.
2.10.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.
The following example illustrates each of these
possibilities:
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.
2.10.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
In 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!
1
Note 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.
2.10.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
When 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."
2.10.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.
Back to index