CLIPS英文全称为C LanguageIntegrated Production System,直译的话就是“C语言集成产生式系统”,是由美国国家航天局约翰逊空间中心人工智能部在1985年推出的专家系统开发工具,设计之初普遍应用于航空航天领域。CLIPS是一种支持多种编程风格的语言,包括:基于规则的、面向对象的和面向过程的编程风格。
1. 介绍
这是CLIPS 6.3基本编程指导手册,主要面向于对CLIPS语法感兴趣的用户。学习本手册出现一些编程语言的知识外,不需要任何专家系统的背景知识。本手册第2节介绍CLIPS语言的整体框架和基本目标。第3节到11节详细介绍CLIPS语言的主要内容,包括类型、规则、面向对象编程(COOL)等。第12节介绍action和function相关的内容,第13节介绍交互界面典型的命令。
本手册仅介绍CLIPS编程的基本知识,对于用户定义函数、嵌入式应用等内容,将在高级编程指南中介绍。用户从本手册可以实现简单的专家系统。
2. CLIPS概述
2.1 CLIPS交互
CLIPS专家系统可以通过三种方式进行交互:1)简单的命令行交互界面;2)提供的IDE交互界面(该方式在Interfaces手册中介绍);3)嵌入到其他的系统被调用(该方式在高级编程指南中介绍)。此外,我们还可以将一系列命令写到一个文件中,在CLIPS启动时读取该文件来自动执行相应的命令;或者可以使用batch命令来批量执行。
CLIPS最常用的交互方式是第一种方式。它的标准使用方法是:
1) 使用文本编辑器构建知识库;
2) 加载知识库到CLIPS;
3) 推理执行;
CLIPS提供接口以便于查看当前系统状态、跟踪执行路径、增加或者移除信息以及清空CLIPS。
2.1.1命令行方式交互
CLIPS使用的最主要交互方式即为命令行方式(顶层命令)。当进入CLIPS交互环境后,会出现“CLIPS>”提示符,输入相应的命令后,该命令将会被解释执行。命令可以是函数调用,结构定义、全局/局部变量定义、常量(包括符号常亮和数字)。如果输入的是函数调用,该函数将被执行并返回对应的执行结果。函数调用使用前缀格式——即先写函数名再跟操作数;结构定义则创建一种新的类型;输入一个全局符号、局部符号或者常量,该符号或者常量的值将被显示出来;局部变量能够在命令交互界面中bind函数改变,它的值将保留到reset或者clear命令被调用。下面是一个简单的例子。
2.1.2 命令自动执行和加载
在一些操作系统中,允许在程序启动时自动加载一些额外的参数。当CLIPS运行在这样的操作系统时,CLIPS将能够在启动时自动从文件中运行相应的命令,加载一些结构定义。
在命令行中启动CLIPS的语法如下所示:
clips
其中:
-f:同batch命令,如果-f的命令文件中含有exit,则当文件中的命令执行完后,自动退出clips;如果没有exit命令,则命令执行完后仍然停留在clips环境中。
-f2:同batch*命令
-l:同load命令
2.1.3 与其他编程语言集成
当使用专家系统时,主要有两种集成方式:将CLIPS嵌入式到其他系统;在CLIPS中调用外部函数;CLIPS两种方式均可以支持,具体内容在高级编程指南中介绍。
2.2 引用手册的语法
该手册使用的术语描述方式与通用计算机引用手册保持一致。文本字符即为字面含义;粗体词或字符表示输入内容的口头描述;使用尖括号包含的连续字符表示用户提供的类对象单个实体。非终结符后加“*”表示用户提供的类对象的0个或多个实体;非终结符后加“+”表示用户提供的类对象的1个或多个实体;两个非终结符之间的省略号也表示这个非终结符出现1次或多次。在中括号内的项(如[
2.3 基本编程元素
CLIPS提供三种基本编程元素:原始数据类型,操作数据的函数,用于添加知识库的结构。
2.3.1 数据类型
CLIPS提供8种原始数据类型:float,integer,symbol,string,external-address,fact-address,instance-name和instance-address。数字主要用float和integer表示,符号主要用symbols和strings表示。
数字仅仅由0~9/./e/+/-组成,CLIPS内部用long表示integer类型,用double表示float类型。同其他编程语言一样,在比较两个浮点数以及比较整数与浮点数时,要注意使用相对差值进行比较,不是直接使用比较运行符进行比较。
整型的示例如下:
237 15 +12 -32
浮点的示例如下:
237e3 15.09 +12.0 -32.3e-7
形式化表示如下:
symbol是字符敏感的,由可打印ASCII字符开始,后跟0到多个可打印的ASCII字符。symbol以非可打印字符(包括空格、Tab、回车、换行)、双引号,”(“,”)”, “&”,”|”,”<”,“~”,“;”作为符号结束标记。“’;”后跟的字符表示注释,如果symbol以“<>”包含,则可以包括符号结束标记。此外,由于“?”和“$?”有特殊含义,符号也不能以这两种类型作为开始字符。以下是字符的一些示例:
foo Hello B76-HIbad_value 127A 456-93-039 @+=-% 2each
注意,其实数字也是symbol的一种特殊情况,只是其特殊性将其单独划为了float和integer两种类型。
string是由双引号括起来的任意字符,如果其内也保护双引号,则使用\”表示,以下是字符串的一席示例:
“foo” “a and b” “你好” “a\”quote”
external-address表示被CLIPS调用的,由其他语言实现的API函数的返回值数据结构。它只在调用外部函数时才会被生成,即不能定义一种这样类型的数据。它的具体使用方式在高级编程手册中介绍。在CLIPS内部,该类型的打印形式为:
其中XXXXX表示外部地址。
fact是一些原子值构成的链表,通过fact索引值或者名字被引用。它在CLIPS内部的打印形式为:
其中XXX表示fact的索引值
instance是一个对象,表示一个实例或者是类的示例。在CLIPS中,对象被定义为float,integer,symbol,string,multifield values,external-address,fact-address或者用户定义类的instance。用户定义类的instance通过make-instance创建,该实例可以通过引用地址唯一标识。所有这些将在2.4.2,2.5.2.3,2.6和第9节中详细介绍。
instance-name是用中括号扩起来的symbol(但外面的中括号并不是instance-name的一部分),因此一般的符号不用中括号括起来。但如果COOL(CLIPS Object Oriented Language)没有包含在CLIPS的配置中,一般的符号也可以使用中括号括起来。
instance-address仅在绑定函数调用返回值到instance-address时或者绑定一个变量到一个匹配了规则左部对象的实例时才会产生。
引用一个用户定义类的实例既可以使用instance-name,也可以使用instance-address,instance-address仅在强调速度时才使用。instance-address在CLIPS内部的打印形式为:
其中XXX为instance的名字。
在CLIPS中,包含一个value(可以是任意一种类型)的占位符称之为field。基本数据类型的value称之为单域值(single-field value)。constant表示该值可以直接表示为一系列字符串的不变单域值。多域值(multifield value)由0个或多个单域值组成,在显示多域值时,CLIPS使用小括号将其括起来。以下是多域值的一些示例:
(a) (1 bar fool) () (x 3.0 “red” 567)
注意”(a)”与“a”是不同的。多域值产生主要有以下几种情况:
1) 调用返回多域值的函数;
2) deffunction、objectmessage-handle、method中定义的通配符;
3) 在模式匹配时绑定变量。
variable时一个存储value的符号地址,它被用在CLIPS的构造类型(如defrule,deffunction,defmethod,defmessage-handle)中,其具体信息将在相应的章节中介绍。
2.3.2 函数
function是CLIPS中一段可执行代码,它使用一个函数名表示,返回有用的值或者产出有用的副作用(如显示相关信息)。本手册里所说的function通常表示返回有用值的函数。
function包括用户定义函数和系统定义函数,他们都是用在外部环境中编写再链接到CLIPS的环境中。系统定义函数已经集成到CLIPS环境中,可以直接调用;用户定义函数是在CLIPS环境外定义的,需要使用extern加入到CLIPS环境中。详细的系统定义函数列表见附录H。
deffunction容许用户在CLIPS环境中直接使用CLIPS的语法定义自己的函数。这类函数同用户外部定义的函数相比,主要区别在于:使用deffunction定义的函数是被CLIPS解释执行的,而用户外部定义函数是在外部编译完成,被CLIPS直接执行的。deffunction的详细内容将在2.5.2.1节中介绍。此外还可以使用defgeneric和defmethod定义通用函数,这些函数实现多态,可以通过参数类型、个数的不同执行不同的代码。通用函数将在2.5.2.2节详细介绍。
CLIPS的函数调用使用前缀表达式表示。下面是一些乘法和加法的示例。
2.3.3 结构
CLIPS中有大量定义结构:defmodule,defrule, deffacts, deftemplate,defglobal, deffunction, defclass, definstances,defmessage-handler, defgeneric和defmethod。结构与函数调用的区别在于:结构定义会影响CLIPS的环境,会往知识库中添加或者删除某些知识,但没有任何输出;函数调用通常不改变CLIPS的环境(除了clear、reset、open file类的函数调用),但会有返回值或者输出。
CLIPS的注释使用“;”标注,“;”后面的该行所有内容均被看成是注释。
2.4 数据抽象
CLIPS有三种形式表示数据抽象:facts,objects和globalvariables。
2.4.1 Facts
事实(Facts)是CLIPS系统中表示信息的一种高层次形式。每个事实表示当前事实列表(Fact-list)中的一段信息。事实是规则(rule)使用数据的基本单元。
事实列表通过assert(添加)、retract(删除)、modify(修改)、duplicate(复制)等命令进行事实操作。事实列表的长度仅受限于主机的内存,如果一个事实已经在事实列表中,除非你修改事实列表的属性(见13.4.4和13.4.5节),否则该事实将被忽略。
一些命令如(retract、modify、duplicate等)需要知道引用具体的事实,此时主要通过事实索引(从0开始)和事实地址进行引用。事实索引在reset或clear命令后,将重新从0开始编号。事实地址主要通过函数调用返回值或者规则左部匹配时进行绑定。
事实标识(fact identifier)是一个简单的显示事实的方式。它的格式为“f-factIndex”。
事实的存储分为有序和无序两种方式。
2.4.1.1 有序方式
有序事实由一个符号域,后跟0个或多个域组成,域之间使用空格分隔,整个事实使用小括号括起来。其中第一个符号可以标识后面域成员之间的关系。如可以使用“(father-of jack bill)”标识bill是jack的父亲这样一个事实。
无序事实第一个域可以是基本类型中的任意类型,且后面域没有先后顺序。但两种类型的事实表示时均不能使用如下符号(这些保留字可以用于slot的名字,但不建议这么使用),这些是CLIPS中的保留字,仅仅用在deftemplate中。
test, and, or,not, declare, logical, object, exists, and forall
2.4.1.2 无序方式
有序事实将信息按照位置进行编码。当需要访问信息时,用户不但需要知道数据存储在哪个事实中,而且还需要知道信息存在在事实的哪个位置的域上。无序事实对每个域给出一个域名称,通过域名称对域中信息进行访问。无序事实通过deftemplate构件进行创建,该构件类似于结构定义。
无序事实与有序事实的区别在于第一个域的符号。所有事实第一个域都是符号,如果该符号对应deftemplate的名字后跟slot信息,则该事实为无序事实,否则如果仅仅通过一个小括号括起来,则为有序事实。下面是有序事实和无序事实的一些示例。
a)无序事实
(client (name "Joe Brown") (idX9345A))
(point-mass (x-velocity 100) (y-velocity-200))
(class (teacher "Martha Jones")(#-students 30) (Room "37A"))
(grocery-list (#-of-items 3) (items breadmilk eggs))
无序事实不关心各个域出现的顺序,下面表示的是同一个事实:
(class (teacher "Martha Jones")(#-students 30) (Room "37A"))
(class (#-students 30) (teacher"Martha Jones") (Room "37A"))
(class (Room "37A") (#-students30) (teacher "Martha Jones"))
b)有序事实
(class "Martha Jones" 30"37A")
(class 30 "Martha Jones""37A")
(class "37A" 30 "MarthaJones")
使用deftemplate定义的事实,除了可以添加和删除外,还可以修改和复制。修改和复制的好处是仅仅改变某些需要变化的域,而不必改变不变的域。
2.4.1.3 初始化事实
deffacts可以初始化一批事实,它在每次reset时均会将相应的事实添加到事实列表。
2.4.2 Objects
在CLIPS中,对象(Object)可以被定为一个符号、一个字符串、一个浮点数、一个整数、一个多域值、一个外部引用地址、一个用户定义类的实例。对象包含两个部分:属性和行为。类(class)是定义对象行为和属性的一个模板,而该类的实例即为对象。下面是一些对象及所属类的例子。
在CLIPS中,对象分为两种:原生类型对象和用户定义类的实例。这两种类型的对象在引用、创建、删除以及属性定义上都不相同。
|
原生类型的对象 |
用户定义类的实例 |
引用方式 |
直接使用对应的值 |
根据名字或者地址引用 |
创建方式 |
CLIPS隐式创建 |
通过message或者专门的函数显示创建 |
删除方式 |
CLIPS隐式删除 |
通过message或者专门的函数显示删除 |
属性定义 |
CLIPS内部预定义 |
通过slot定义 |
使用方式 |
用于General function |
用于OOP |
对象slots和事实模板的主要区别在于继承的概念。继承使得对象的属性和行为来自于父类的定义。COOL支持多继承,一个类可以继承自多个父类。由于继承通常用于继承slot和message-handle,而基本类型(如float、integer等),没有slot和message-handle,因此通常不会从基本类型继承。
2.4.2.1 初始化对象
通过definstances初始化一批对象,在调用reset时会自动将对象实例加入到实例链表(instance-list)中。
2.4.3 全局变量
全局变量定义后,容许在CLIPS整个环境中使用,同C语言中的全局变量,但CLIPS中的全局变量是弱类型的,即它并不限定为某个固定类型。与此不同,在一个构件中(如defrule、deffunction),可以定义局部变量,该变量仅能在定义的结构内使用。
2.5 知识表示
CLIPS提供启发式范式和过程式范式表示知识。本节将介绍这两种知识表示。面向对象知识表示将在下一节介绍。
2.5.1 启发式知识表示——规则(rules)
规则(rule)是CLIPS中表示知识的一种主要方式,它用来表示启发式的知识,即在某种条件成立的情况下执行一系列的操作。专家系统开发人员通过定义一系列规则以联合起来解决一个问题。一条规则由前部和后部两部分组成。规则的前部也叫做左边(LHS),表示“如果”的部分,是规则被激活的必要条件;规则的后部也称为右边(RHS),表示“则有”的部分,表示规则成立时执行的操作。
规则左部的每个事实或者实例成立与否主要查看它是否在事实列表(fact-list)上或者在实例列表(instance-list)上,它通过模式匹配,利用推理机自动完成事实和实例匹配与否的检测。
规则右边表示的一系列操作在规则满足时执行,当有多个规则同时满足时,推理机使用冲突解决策略选择其中的某条规则进行执行。当这条规则执行完毕后,推理机再选择下一条规则进行执行,直到没有待执行的规则为止。
在很多情况下,规则被认为是过程式编程(如C语言)的IF-THEN结构,只是在过程式编程时,IF-THEN结构仅在控制流执行到该语句时才执行,而CLIPS的规则是一直会检测,所以CLIPS的规则更像是WHENEVER-THEN。推理机总是保持了那些条件成立的规则路径,因此规则在他们满足时能够立即执行,从这一点上说,规则又有点像Ada中的异常处理。
2.5.2 过程性知识表示
CLIPS也像C、Ada语言一样支持过程性范式来表示知识。deffunctions和通用函数定义允许用户定义新的执行单元以产生有用的副作用和返回值。用户可以像调用CLIPS的内建函数一样调用这些新的函数。message-handlers容许用户通过定义对消息的响应来定义对象的行为。defunctions、generic functions和message-handlers都是用户定义的过程化代码片段,它们被CLIPS在合适的时间内解释执行。defmodules将不同的知识库分隔开来。
2.5.2.1 Deffunctions
用户通过deffunction在CLIPS环境中直接定义新的函数。在以前的CLIPS版本中,仅容许用户通过在CLIPS外使用其他语言(如C、Ada等)定义函数,然后再通过外部函数调用的方式链接到CLIPS环境中。deffunction的函数定义了一系列表示式(如规则中的RHS),当该函数被调用时,由CLIPS依次按序执行。函数的返回结果是最后一个表示式的值。调用deffunction函数就像调用CLIPS中其他函数一样。其具体内容将在第7章中介绍。
2.5.2.2 Generic Functions
Generic Functions同deffunction类似,也能在CLIPS中直接定义并被其他采用相同的方式调用。与deffunction不同的是,它能支持重载。你可以通过类型、参数个数的不同定义同名不同义的函数。具体内容将在第8章中介绍。
2.5.2.3 对象信息传递(Object Message-Passing)
对象包括两个方面:属性和行为。对象属性即对象类的slots,slots即为2.4.2中介绍的内容。对象行为是根据该对象链接的message-handlers操作定义的。对象通过消息传递进行操作。如要使得Rolls-Royce对象(它是CAR类的一个实例)发动引擎,用户可以调用“send”函数向Rolls-Royce发送“start-engine”的消息。Rolls-Royce对象收到该消息后,将该消息传递给其所在类(即CAR类)及其父类的“start-engine”消息的处理函数message-handler,从而完成相应的功能。message的处理结果同函数调用,它将产生有效的副作用或者返回一个有用的值。更深层次的内容将在2.6和第9章中介绍。
2.5.2.4 defmodules
defmodules能够将不同的知识库区分开来。每一个构件都必须从属于一个模块(module)。程序员能够显示的控制模块中的哪个构件对其他模块可见,模块可以见到其他模块中的哪些构件。不同模块间事实和实例的可见性也是相似的控制。模块也可以控制规则的执行流。该部分具体内容将在第10章中介绍。
2.6 CLIPS面向对象语言
这节将简要介绍CLIPS中面向对象语言(COOL)。COOL包括数据抽象和知识表示。这节将给出COOL的整体介绍,主要包括COOL中两个方面概念的接受。用户定义类的实例请参考2.3.1节,对象结构请参考2.4.2和2.5.2.3节。COOL的详细内容将在第9章中介绍。
2.6.1 COOL与纯的面向对象编程范式的不同
在纯面向对象编程中,程序中所有元素都是对象,对象仅能通过消息进行操作。而在CLIPS中,对象仅限制于以下内容:floating-point and integer numbers, symbols, strings,multifield values, external-addresses, fact-addresses and instances of user-defined classes。所有的对象都可以通过消息进行操作,而instances of user-defined classes必须通过消息才能操作。如在纯OOP系统中要将两个数加起来,你需要先发送“add”信息给第一个数对象,并将第二数对象作为参数传递给第一个数对象。在CLIPS中,你可以调用“+”函数,将两个数作为函数的两个参数传递进去,也可以像纯OOP系统一样,在NUMBER类上定义消息处理函数。
所有不是对象的程序元素,你智能使用非OOP的函数来操作。如打印一条规则,你只能调用ppdefrule函数,而不能发送“print”给rule,因为rule不是对象。
2.6.2 面向对象编程的主要特征
面向对象主要包括以下5个特征:抽象(abstraction)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、动态绑定(dynamic binding)。抽象是高层的、直观的表示复杂的概念。封装是将处理过程实现细节掩藏起来,对外提供良定义的接口。继承是指一个类可以从其他类的定义中继承属性和行为。多态是指不同对象采用各自独特的方式处理相同的message。动态绑定是指只有在运行时才能推断使用哪个message-handle来处理message。
COOL定义的新类抽象为新的数据类型。类型中的slots和message-handlers表示了该类对象的属性和行为。
COOL支持封装,用户定义类的实例操作必须通过信息传递。没有定义相应message-handler方法的实例不能处理对应消息。
COOL支持多继承,其定义的类可以继承自多个父类。所有类均有一个继承链,如果链上有相同定义的属性和行为,则使用离该类最近的父类属性及行为。
COOL支持多态,它通过在不同的对象上绑定不同message-handler(但使用相同的名字)实现。
COOL的动态绑定表现在:在send函数调用的对象影响直到运行时才绑定。例如,一个实例名或变量在message发送时指向一个对象,但在另一个时刻可能指向另一个对象。
2.6.3 实例集查询和分布是动作
为增强规则应用于对象的模式匹配,COOL提供了一个有用的查询系统,以决策、组合和执行用户实例定义的一组操作,满足用户定义的标准。查询系统容许你关联到任意实例。你可以使用查询系统决定:一个特殊的关联集是否存在,你能保存该集合以便于后续引用,或者你可以在该集合上依次迭代执行一个操作。其中一个应用的场景可能是:找出集合中所有年龄相同的男孩女孩对。