CIS341 Lab Exercises 2006: Jess Tutorial

Updated Feb 27th 2006.

If you have any questions, try finding the answer on the Jess documentation before asking the tutor.

Facts and Rules in Jess

Jess, or the Java Expert System Shell as it was first known, is rather more than the name suggests, comprising a general-purpose programming language which can access all Java classes and libraries.

However, don't let the word "Java" put you off: we will start with the basic constructs fact and rule which are enough to develop simple applications without having to do any procedural programming. Unfortunately the Jess documentation does not take the same approach, and expects you to master the more fiddly stuff before getting to the basics of facts and rules.

Before attempting the exercises at the bottom of this page, you should run all the examples below so you can start getting a feel for how Jess applications work.

Starting up Jess

If you are working on a lab machine you just need to click on the "jess" shortcut which should be somewhere on the desktop. You will then see a command-line mode window with the prompt:

Jess>

To be sure it's working, enter the following elementary Jess program:

(printout t "Hello World!" crlf)

What do you think crlf means?

(If you have a trial version of Jess installed on your own machine, one way to start it up is to open a command-mode window, cd to the Jess folder and enter java jess.Main or java jess.Console.)

Building a Jess application

For your first Jess application you need to do the following things:

  1. Clear working memory by entering the (clear) command;
  2. Create some facts using the (assert) command;
  3. Create some rules using the (defrule) command;
  4. Run the application by entering (run)

Here's an example:

(clear)
TRUE 
Jess> (assert(smoke))
<Fact-0> 
Jess> (defrule fire1 (smoke) => (assert(fire)) )
TRUE 
Jess> (run)
1 
Jess> (facts)
f-0   (MAIN::smoke) 
f-1   (MAIN::fire) 
For a total of 2 facts. 
Jess> 

Rules

The general format of a rule definition is:

(defrule 'name' LHS => RHS) where

LHS is a pattern or combination of patterns which are compared against any facts in working memory;
RHS is a function which is executed when the rule 'fires', i.e. the LHS matches some facts(s) in working memory.

Note the use of the assert function in the above example. If the rule looked like this:

(defrule fire1 (smoke) => (fire) )

then Jess would try to execute a function called 'fire' rather than adding the fact (fire) to working memory.

The LHS can be a Boolean combination for example:

and
(defrule haircut (pensioner)(thursday)=>(give_discount))
(defrule haircut (and (pensioner)(thursday))=>(give_discount))
or
(defrule cinema_ticket (or (pensioner)(student)(unemployed)) => (give_discount))
not
(defrule traffic_light (not(green)) => (stop))

Facts

We are using the simplest variety of Jess facts here, namely ordered facts, consisting of a list of atoms between parentheses. Later in the course we will look at unordered facts and shadow facts.

Notice that Jess gives each fact a number: 0 is 'smoke', 1 is 'fire'.

You can remove facts from working memory by entering (retract n), where n is the fact's number.

Try running the example above, and then retract the fact 'smoke' and enter (facts) again. What do you notice?

Now clear your working memory and type in the following example.

Jess> (clear)
TRUE 
Jess> (defrule fire2 (logical (smoke)) => (assert(fire)) )
TRUE 
Jess> (assert(smoke))
<Fact-0> 
Jess> (run)
1 
Jess> (facts)
f-0   (MAIN::smoke) 
f-1   (MAIN::fire) 
For a total of 2 facts.

Now retract 'smoke' again and enter (facts). Do you notice anything different?

Rules with variables

Variables are represented in Jess as ?variable_name where variable_name is a string made up of alphanumeric characters, dashes (-) and underscores (_). Variables are untyped and are usually declared implicitly when they are first given a value. Here's a familiar example:


(reset)
TRUE 
Jess> (assert (man socrates))
<Fact-1>
Jess> (defrule mortality (man ?x) => (assert(mortal ?x)) )
TRUE 
Jess> (run)
1 
Jess> (facts)
f-0   (MAIN::initial-fact) 
f-1   (MAIN::man socrates) 
f-2   (MAIN::mortal socrates) 
For a total of 3 facts.

Recursive rules

Here's an example of an effectively recursive rule:

(defrule anc (or (parent ?a ?b) (and (parent ?a ?c)(ancestor ?c ?b)) )
=> (assert(ancestor ?a ?b)))

(assert(parent andy betty))
(assert(parent betty charlie))
(assert(parent charlie donna))

Before running this, write out what you think the results should be.

Loading Jess programs from files

You don't have to type every line of Jess programs at the prompt before you can run them. You can also load them from files using the (batch) command, for example:

Jess> (batch "C:/Jess/Jess61p8/CIS341/socrates.txt")

Note: you should use the forward slash '/' whether you're working on Unix or Windows, and it's generally safer to put double quotes around the path name.

Loading a file like this is equivalent to typing each line manually: nothing will happen until you enter (run) (unless this was in the file of course).

More about facts

Another way to get facts into the working memory is by using the deffacts command followed by (reset):
Jess> (deffacts flowers (roses red) (violets blue))
TRUE 
Jess> (facts)
For a total of 0 facts. 
Jess> (reset)
TRUE 
Jess> (facts)
f-0   (MAIN::initial-fact)
f-1   (MAIN::roses red) 
f-2   (MAIN::violets blue) 
For a total of 3 facts.

Queries

A query is defined as a rule with no RHS. In the following example the query search returns all values of ?Y for which a fact (ancestor ?X ?Y) can be found in working memory, where a value for ?X is given at runtime.
(defrule anc (or (parent ?a ?b) (and (parent ?a ?c)(ancestor ?c ?b)) )
=> (assert(ancestor ?a ?b)))

(deffacts family
(parent andy betty)
(parent betty charlie)
(parent betty edna)
(parent charlie donna)
(parent edna freddy))

(defquery search
    "Finds ancestor facts with a specified first field"
    (declare (variables ?X))
    (ancestor ?X ?Y))
There are two complications: first, queries only examine working memory and will not automatically invoke recursive rule application; secondly the result is returned in a form that still requires some fiddly Jess coding to extract it. The example below is taken directly from the Jess documentation, and you should read further in section 2.9.
Jess> (reset)
TRUE 
Jess> (run)
11 
Jess> (bind ?it (run-query search andy))
<External-Address:java.util.AbstractList$Itr>
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))
freddy 
donna 
edna 
charlie 
betty 
FALSE 
Jess> 

Exercises

  1. Code the "bathroom" scenario from the lecture notes as Jess facts and rules. Check that it produces the expected answers. Try extending this to other domestic disasters.
  2. (added 27-02-06) Now redo the "bathroom" exercise using deffacts with ordered facts such as:
    (state hall wet)
    (state kitchen dry)
    (is-raining false)
    (location leak kitchen)
    
    Define a query which will return the location of the leak.
  3. Code a family tree as a series of facts defining immediate family members such as (mother ?a ?b), (husband ?x ?y). You could make it a subtree of this example. Write rules for relationships such as uncle, cousin, grandparent. Define queries to find out who someone's cousins, parents etc are.