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:
-
Put the most specific patterns (those that will match the fewest
facts) near the top of each rule's LHS.
-
Put the most transient patterns (those that will match facts that
are frequently retracted and asserted) near the bottom of a LHS.
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 defrule 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 youa 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.