3. Programming in the Jess Language

Useful expert systems can be written using the Jess language, with no extensions. I won't present a tutorial on writing such systems here (maybe someday!), but I do want to share a few useful hints and ideas in the following sections.

3.1. Using an External Editor

Jess allows you to enter rules and other code directly at its interactive prompt. While this is fine for experimenting, Jess doesn't yet have the ability to save the source text for all the rules and constructs you enter. Therefore, you will typically enter your rules and other data into a separate script file and read it into Jess using the batch command. Jess does offer the ppdefrule and save-facts commands, both of which can be very helpful in interactively building up a system definition and then storing it in a file. Note that you might use the system command to start the external editor from within Jess, if desired.

3.2. Efficiency of rule-based systems

The single biggest determinant of Jess performance is the number of partial matches generated by your rules. You should always try to obey the following (sometimes contradictory) guidelines while writing your rules: You can use the view command to find out how many partial matches your rules generate. See this chapter on How Jess Works for more details.

3.3. Error Reporting and Debugging

I'm constantly trying to improve Jess's error reporting, but it is still not perfect. When you get an error from Jess (during parsing or at runtime) it is generally delivered as a Java exception. The exception will contain an explanation of the problem and the stack trace of the exception will help you understand what went wrong. For this reason, it is very important that, if you're embedding Jess in a Java application, you don't write code like this:
        try
        {
          Rete engine = new Rete();
          engine.executeCommand("(gibberish!)");
        }
        catch (JessException re) { /* ignore errors */ }
If you ignore the Java exceptions, you will miss Jess's explanations of what's wrong with your code. Don't laugh - more people code this way than you'd think!

Anyway, as an example, if you attempt to load the folowing rule in the standard Jess command-line executable,
        Jess> (defrule foo-1
                (foo bar)
                ->
                (printout "Found Foo Bar" crlf))
You'll get the following printout:
  Jess reported an error in routine Jesp.parseDefrule.
    Message: Expected '=>' .
    Program text: ( defrule foo-1 ( foo bar ) ->  at line 2.
          at jess.Jesp.parseError(Jesp.java:1434)
          at jess.Jesp.doParseDefrule(Compiled Code)
          at jess.Jesp.parseDefrule(Jesp.java:882)
          at jess.Jesp.parseSexp(Jesp.java:153)
          at jess.Jesp.parse(Compiled Code)
          at jess.Main.execute(Compiled Code)
          at jess.Main.main(Main.java:26)
This exception, like all exceptions reporte by Jess, lists a Java routine name. The name parseDefrule makes it fairly clear that a rule was being parsed, and the detail message explains that -> was found in the input instead of the expected => symbol (we accidentally typed -> instead). This particular error message, then, was fairly easy to understand.

Runtime errors can be more puzzling, but the printout will generally give you a lot of information. Here's a rule where we erroneously try to add the number 3.0 to the word four:
        Jess> (defrule foo-2
                =>
              (printout t (+ 3.0 four) crlf))
This rule will compile fine, since the parser doesn't know that the + function won't accept the atom four as an argument. When we (reset) and (run), however, we'll see:
Jess reported an error in routine Value.numericValue
  while executing (+ 3.0 four) while executing (printout t (+ 3.0 four) crlf)
  while executing defrule foo-2 while executing (run).
  Message: Not a number: "four" (type = ATOM).
  Program text: ( run )  at line 4.
        at jess.Value.typeError(Value.java:361)
        at jess.Value.typeError(Value.java:356)
        at jess.Value.numericValue(Value.java:244)
        at jess.Plus.call(Compiled Code)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.FuncallValue.resolveValue(FuncallValue.java:33)
        at jess.Printout.call(Compiled Code)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.Defrule.fire(Compiled Code)
        at jess.Activation.fire(Activation.java:58)
        at jess.Rete.run(Compiled Code)
        at jess.Rete.run(Compiled Code)
        at jess.HaltEtc.call(Funcall.java:1559)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.Jesp.parseAndExecuteFuncall(Jesp.java:1423)
        at jess.Jesp.parseSexp(Jesp.java:172)
        at jess.Jesp.parse(Compiled Code)
        at jess.Main.execute(Compiled Code)
        at jess.Main.main(Main.java:26)
In this case, the error message is also pretty clear. It shows the offending function (+ 3.0 four) then the function that called that (printout) then the context in which the function was called (defrule foo-2), and finally the function which caused the rule to fire (run).

Looking at the stack trace, starting from the top down, you can find entries for the + fucntion (Plus.call()), the printout function, the rule firing (Defrule.fire()) and the run command (Rete.run()).

The message 'Not a number: "four" (type = ATOM).' tells you that the + function wanted a numeric argument, but found the symbol (or ATOM) four instead.

If we make a similar mistake on the LHS of a rule:
        Jess> (defrule foo-3
                (test (eq 3 (+ 2 one)))
                 =>
                )
We see the following after a reset:
  Jess reported an error in routine Value.numericValue
    while executing (+ 2 one) while executing (eq 3 (+ 2 one))
    while executing 'test' CE while executing rule LHS (TECT) while executing (reset).
    Message: Not a number: "one" (type = ATOM).
    Program text: ( reset )  at line 4.
        at jess.Value.typeError(Value.java:361)
        at jess.Value.typeError(Value.java:356)
        at jess.Value.numericValue(Value.java:244)
        at jess.Plus.call(Compiled Code)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.FuncallValue.resolveValue(FuncallValue.java:33)
        at jess.Eq.call(Compiled Code)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.FuncallValue.resolveValue(FuncallValue.java:33)
        at jess.Test1.doTest(Test1.java:95)
        at jess.NodeTest.runTests(Compiled Code)
        at jess.NodeTest.callNode(Compiled Code)
        at jess.Node.passAlong(Compiled Code)
        at jess.Node1TECT.callNode(Compiled Code)
        at jess.Rete.processTokenOneNode(Rete.java:1009)
        at jess.Rete.processToken(Compiled Code)
        at jess.Rete.assert(Rete.java:754)
        at jess.Rete.reset(Rete.java:680)
        at jess.HaltEtc.call(Funcall.java:1565)
        at jess.FunctionHolder.call(FunctionHolder.java:35)
        at jess.Funcall.execute(Funcall.java:238)
        at jess.Jesp.parseAndExecuteFuncall(Jesp.java:1423)
        at jess.Jesp.parseSexp(Jesp.java:172)
        at jess.Jesp.parse(Compiled Code)
        at jess.Main.execute(Compiled Code)
        at jess.Main.main(Main.java:26)
Again, the error message is very detailed, and makes it clear, I hope, that the error occurred during rule LHS execution, in a test CE, in the function (+ 2 one). Note that Jess cannot tell you which rule this LHS belongs to, since rule LHSs can be shared.

3.4. Putting Java Objects into Fact Slots

You can easily put Java objects into the slots of Jess facts, as described elsewhere in this document. This section describes some minimal requirements for objects used in this way.

Jess uses the equals and hashCode methods of any objects added to a slot in a Jess fact. As such, it is very important that these methods be implemented properly. The Java API documentation lists some important properties of equals and hashCode, but I will reiterate the most important (and most often overlooked) one here: if you write equals, you probably must write hashCode too. For any two instance of a class for which equals returns true, hashCode must return the same value. If this rule is not observed, Jess will appear to malfunction when processing facts containing these improperly defined objects in their slots. In particular, rules that should fire may not do so.

Back to index