声明事实(duck-sound quack),然后用(run)命令运行程序,检查规则,你将会发现这条规则产生了新的事实(sound-is quack),这是因为,变量?sound已经被约束到quack了。
当然,你可以多次使用一个变量。举例说明,输入下面的代码,别忘了输入(reset)命令和重新声明(duck-sound quack)。
(defrule make-quack
(duck-sound ?sound) =>
(assert (sound-is ?sound ?sound)))
当该条规则被触发,将会产生事实(sound-is quack quack),这样变量?variable就被用到两次了。
鸭子说了什么
变量也通常被用在打印输出中,如下:
(derule make-quack
(duck-sound ?sound) =>
(printout t “The duck said” ?sound crlf))
执行(reset)命令后,输入上面的规则,声明事实并运行(run)看看鸭子到底说了些啥?如果你修改这条规则,在输出中用双引号括住quack,会有怎样的结果呢?
一个模式中可能有一个或多个变量,如下例所示:
CLIPS>(clear)
CLIPS>(defrule whodunit
(duckshoot ?hunter ?who) =>
(printout t ?hunter “shot” ?who crlf)) CLIPS>(assert (duckshoot Brian duck))
Brian shot duck ; 今晚有鸭子吃了! CLIPS>(assert (duckshoot duck Brian))
duck shot Brian ; 今晚吃Brian!
CLIPS>(assert (duckshoot duck)) ; 丢失第三个字段
CLIPS> ; 规则不被触发,无输出
注意上面字段顺序的不同将会决定谁射杀谁。同时你也可以看到当事实(duckshoot duck)被声明后,规则并没有被触发。当事实中的字段不能与规则的第二个模式约束?who匹配时,规则不能被激活。
快乐的单身汉
撤销在专家系统中非常有用,通常被用在RHS中要多于顶层中。在一条事实能被撤销之前,它必须被指定给CLIPS。撤销一条规则中的事实,LHS中事实地址(fact-address)首先必须被约束到一个变量。
绑定一个变量到事实的内容与绑定一个变量到事实地址有很大的不同。在上面的例子中,你已经看到了事实(duck-sound ?sound),字段的值被约束了一个变量。因此,?sound被约束到quack。然而,当你想移除包含(duck-sound quack)的事实时,你必须首先告知CLIPS被撤销事实的地址。
事实地址指定使用左箭号(left arrow):“<-”。输入该符号,只要键入一个“<”符号,然后紧跟一个“-”即可。从一个规则中撤销事实的例子如下:
CLIPS>(clear)
CLIPS>(assert (bachelor Dopey))
CLIPS>(facts)
f-0 (initial-fact) f-1 (bachelor Dopey) For a total of 2 facts. CLIPS>(defrule get-married
?duck <- (bachelor Dopey) =>
(printout t “Dopey is now happily married” ?duck crlf) (retract ?duck)) CLIPS>(run)
Dopey is now happily married
f-0 (initial-fact)
For a total of 1 fact. CLIPS>
注意到,左箭号将事实的地址约束到?duck,因此,(printout)打印出?duck的事实索引。同样的,事实(bachelor Dopey)也已被撤销。
变量也能被用来拾取事实的值同时作为地址。如下例所示,为了简便,用到自定义事实(deffact)。
CLIPS>(clear)
CLIPS>(defrule marriage
?duck <- (bachelor ?name) =>
(printout t ?name “is now happily married” crlf) (retract ?duck))
CLIPS>(deffacts good-prospects
(bachelor Dopey) (bachelor Dorky) (bachelor Dicky)) CLIPS>(reset) CLIPS>(run)
Dicky is now happily married Dorky is now happily married Dopey is now happily married CLIPS>
注意上面的所有的事实均与模式(bachelor ?name)匹配,规则被触发。CLIPS还有一个名为事实索引(fact-index)的函数,该函数用来返回事实地址的事实索引。
通配符
代替绑定一个变量到一个字段值,一个非空字段的存在能被检测到单独使用通配符(wildcard)。举个例子,假设你正在经营一个鸭子约会服务部,一只母鸭声明它只与名字为Richard的公鸭约会。事实上,关于这个声明有两个标准,因为它的隐含意义是鸭子必须有不止一个的名字,因此这样一个简单的事实声明:(bachelor Richard)是不充足的,因为在该事实中仅有一个名字。
部分事实被指定的情形,是非常普遍和重要的。为了解决这个问题,可以利用通配符来触发Richard们。
最简单的通配符格式被称之为单字段通配符(single-field wildcard),以一个问号“?”来表示。问号也被称为单字段约束(single-field constraint)。一个单字段通配符仅代表一个字段,如下所示:
CLIPS>(clear)
CLIPS>(defrule dating-ducks
(bachelor Dopey ?) =>
(printout t “Date Dopey” crlf)) CLIPS>(deffacts duck
(bachelor Dicky) (bachelor Dopey)
(bachelor Dopey Mallard) (bachelor Dinky Dopey)
(bachelor Dopey Dinky Mallard)) CLIPS>(reset) CLIPS>(run) Date Dopey CLIPS>
模式中包含有一个通配符,指明Dopey的姓氏并不重要,只要名字是Dopey,规则就会被触发。因为模式包含三个字段,其中之一是一个单字段通配符,所以只能是有且仅有三个字段的事实才能满足,只有Dopey的有且仅有两个字的鸭子才能符合这只母鸭的要求。
假设你想指定名字有且仅有三个字的Dopey,那么你应该按照如下格式书写模式:
(bachelor Dopey ? ?)
或者,只要是中间名为Dopey有三个字的都可满足:
(bachelor ? Dopey ?)
或者,只要是姓Dopey有三个字的都可满足:
(bachelor ? ? Dopey)
另一个可能出现有趣的事情是,如果Dopey必须是名字的第一个字,但那些Dopey们仅只能接受两个或三个字的名字。一个解决此问题的方法是写两个规则,如下所示:
(defrule eligible
(bachelor Dopey ?) =>
(printout t “Date Dopey” crlf))
(defrule eligible-three-names
(bachelor Dopey ? ?) =>
(printout t “Date Dopey” crlf))
输入并运行,你将看到那些包含Dopey有两个或三个字的名字被打印出来。当然,如果你想匿名约会日期,那么你需要将Dopey名绑定一个变量并打印出来。
继续讲通配
将规则分开书写而不处理每个字段,用多字段通配符(multifield wildcard)将会更容易。多字段通配符的符号是在问号前面加上一个美元符号,为“$?”,该符号指代零个或多个字段。注意与指代一个且仅为一个的单字段通配符的区别。
上面分开而写的两个规则,此时便可以写成一个了,如下所示:
CLIPS>(clear)
CLIPS>(defrule dating-ducks
(bachelor Dopey $?) =>
(printout t “Date Dopey” crlf)) CLIPS>(deffacts duck
(bachelor Dicky) (bachelor Dopey)
(bachelor Dopey Mallard) (bachelor Dinky Dopey)
(bachelor Dopey Dinky Mallard)) CLIPS>(reset) CLIPS>(run) Date Dopey Date Dopey Date Dopey CLIPS>
通配符的另外一个作用是,它可附属于一个符号字段来创建一个变量,如?x,$?x,?name或者$?name。依照LHS中“?”或 “$?”的使用,变量可以是单字段变量或多字段变量。注意在RHS中,只能用?x,这里的x可以是任意名。你可以将“$”理解成一个函数,函数的参数是一个单字段通配符或者一个单字段变量,分别返回多字段通配符或多字段变量。
作为一个多字段变量的例子,因为一个变量与匹配的字段名等同,下面的规则同样打印出匹配的事实字段名:
CLIPS>(defrule dating-ducks
(bachelor Dopey $?name) =>
(printout t “Date Dopey” ?name crlf)) CLIPS>(reset) CLIPS>(run)