6.20.前向链接和后向链接(forward and backward chaining)
目前为止所介绍的都是前向forward-chaining rule,其基本含义是rule被视为类似if...then的表述。engine被动的执行位于RHS的被激发的rule。一些基于rule的系统,像著名的Prolog语言及其派生语言,都支持backward chaining。在backward chaining系统中,rule仍然以if...then的表述形式,但engine会设法激活那些前提条件(precondition)并不满足的rule。这种做出称为"goal seeking"。Jess同时支持forward和backward chaining。注意:在这里简化了Jess中关于backward chaining的描述,详见underlying algorithms一节。
使用Jess中的backward chaining时,必须先声明那些fact将被backward chaining重复激发。可以在定义template时做这一声明:
Jess> (deftemplate factorial (declare (ordered TRUE) (backchain-reactive TRUE))
同样也可以在定义template后使用do-backward-chaining函数:
Jess> (do-backward-chaining factorial)
然后可以定义匹配这样的pattern的rule。注意:必须在定义其他使用template的rule之前将该template与backward chaining reactive相关联。
Jess> (defrule print-factorial-10 (factorial 10 ?r1) => (printout t "The factorial of 10 is " ?r1 crlf))
当rule编译器发现一个pattern与一个backward chaining reactive template相匹配时,就重写rule并向rule的LHS的内部表述中插入一些特定代码。这些代码添加一个类似下面的fact到fact-list中:
(need-factorial 10 nil)
当rule引擎重启时,没有该pattern的匹配。fact包括重新激活的pattern的头部,并以"need-"作为前缀。
现在可以编写与这些need-(x) fact相匹配的rule了:
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)))
rule编译器也按如下方式重写rule:为该factorial(阶乘) pattern在rule的LHS添加一个不匹配的值。
最终可以编写出符合各种要求(如阶乘)的rule,当由于缺少一个(factorial)fact而不能fire时,任何(need-factorial)rule都可能被激发。如果这些rule开始fire了,则所需的fact会出现,并执行(fire)。由此形成backward chain。Jess将反向链接所有reactive pattern。如:
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 $?) 42 => (assert (bar C D))) TRUE Jess> (reset) TRUE Jess> (run) foo 3
在上例中,一开始没有一条rule被激发。Jess发现当有一个合适的foo fact时,rule-1可以被激发,于是就生成一个(need-foo nil nil)请求。这样可以匹配(由于缺少一个bar fact而不能激发的)create-foo rule的LHS中的一部分。因此Jess又生成一个(need-bar nil nil)请求。由此匹配create-bar的LHS,该rule执行(fire)并asserts (bar C D)。随后激发并fire了create-foo,并asserts (foo A B)。最终导致激发并fire了rule-1。
有一个特殊的条件元素(explicit),通过它可以环绕约束pattern在反向时不会逆转。
6.21.Defmodules
一个典型的基于rule的系统可以随意的包含上百条rule,而一个大型的系统甚至可以包含上千条rule。开发如此复杂的一个系统可能是一项比较艰巨的任务,同时使这些rule之间的交互更为困难。
这也许会让你想到要将rule分割为易于管理的整块来解决这一难题。Module的作用就是将rule和template分割为独立的组群。拥有列出结构的命令可以指定模块(module)的名称,
同时可以一次一个的对模块进行操作。如果没有明确指定一个模块,这些命令(以及其它命
令)默认情况下作用在当前的模块上。如果没有明确的定义任何模块,则当前的模块总是被命名为主模块(main module),即MAIN。目前为止所介绍的所有的结构都定义在MAIN中,因此在Jess中显示时常以"MAIN::"作为开头。
除了能帮助管理数量巨大的rule以外,模块还能提款一种控制机制:仅当模块被选中时,该模块中的rule才会fire,并且同一时间内只能选中一个模块。
CLIPS用户请注意:Jess的defmodule结构类似于CLIPS中的一个同名结构,但并不完全一样。
Jess的语法和名称定义机制比CLIPS简单。两者focus机制基本一致。
6.21.1.模块中的结构定义
可以使用defmodule结构定义一个新的模块:
Jess> (defmodule WORK) TRUE
通过在定义时指定模块名,可以在模块中插入deftemplate,defrule或deffact等结构:
Jess> (deftemplate WORK::job (slot salary)) TRUE Jess> (list-deftemplates WORK) WORK::job For a total of 1 deftemplates in module WORK.
一旦定义完一个模块后,该模块就成为当前模块:
Jess> (get-current-module) MAIN Jess> (defmodule COMMUTE) TRUE Jess> (get-current-module) COMMUTE
如果没有指定模块,则所有定义的deffact,template及rule将自动加入当前的模块:
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 <- (bus (route-number 76)) (have-correct-change) => (get-on ?bus))"
可以通过使用set-current-module函数来改变当前模块并指定其他模块。在commute模型中隐含了have-correct-change模板,因为所有的rule的定义都在其中。