You are on page 1of 131

• CLIPS 用户向导

2007.12.31 Joseph C. Giarratano, Ph.D.

自述文件

通往智慧的第一步是你得承认你的无知,其次是你不必让全世界都知

道你的无知。

这部分被称作序言,但是在还没有人读过它之前,我将它重新命名成

一个惯用的标题,以便让计算机用户选择性的遵从。另一个建议是将之命

名 为 :“ 别 读 我 ”章 节 ,但 如 今 的 人 们 相 信 所 有 他 们 阅 读 过 的 一 切 ,我 恐 怕

他们真的就不读它了。

序言的目的,噢,抱歉,我应该称之为自述文件,它提供了书本中所

包含知识的元知识。之所以称谓为元知识,是指它是关于知识的知识。所

以 关 于 自 述 文 件 的 描 述 事 实 上 我 们 得 称 之 为“ 元 元 知 识 ”。如 果 你 被 搞 糊 涂

了或者你对这些不感冒,那么你可以跳开去选择从书中任何地方看起,因

为我得照顾到所有我的读者。

CLIPS 是什么?

CLIPS 是 一 种 专 家 系 统 工 具 , 最 初 由 NASA/Lyndon B. Johnson 太 空 中

心 软 件 技 术 研 究 室 开 发 出 来 。自 1986 年 首 次 发 布 以 来 ,CLIPS 经 历 了 不 断

的改进和完善。现在它已经被广泛的应用在数以万计的全球用户中。

CLIPS 被 开 发 出 来 以 促 进 集 成 人 类 知 识 和 经 验 的 软 件 发 展 。

在 CLIPS 中 , 知 识 的 表 示 有 三 种 方 式 :

 规则,规则表示法是基于启发式经验知识库的首要选择。

 自定义函数和通用函数,这种方式是程序式知识表示的首选。
 面 向 对 象 设 计 ,也 是 程 序 式 知 识 表 示 的 首 选 。面 向 对 象 的 程 序 设

计 被 支 持 的 5 个 普 遍 接 受 的 特 征 是 :类 ,消 息 处 理 函 数 ,抽 象 ,封

装,继承和多态性。模式匹配可以是对象和事实。

你可以仅用规则,或者仅用对象或者两者混合使用来开发软件。

CLIPS 同 时 支 持 与 其 他 语 言 的 集 成 ,如 C 和 Java。事 实 上 ,CLIPS 是 C

Language Integrated Production 的 缩 写 。 规 则 能 基 于 事 实 与 对 象 的 匹 配 , 规

则和对象同时组成了一个集成系统。除了被当作一个独立的工具之外,

CLIPS 还 能 被 程 序 语 言 调 用 , 运 行 其 函 数 , 然 后 返 回 给 调 用 函 数 控 制 权 。

同 样 的 , 程 序 代 码 也 能 作 为 一 个 外 部 函 数 在 CLIPS 中 被 定 义 和 调 用 。 当 外

部 代 码 执 行 完 毕 后 , 控 制 权 返 回 到 CLIPS。

如 果 你 已 经 对 面 向 对 象 的 程 序 设 计 语 言 如 C++,Smalltalk,Objective C

或 者 Java 很 熟 悉 了 ,想 必 你 已 经 了 解 面 向 对 象 在 软 件 开 发 中 的 诸 多 优 点 了 。

如 果 你 并 不 熟 悉 , 你 将 会 发 现 CLIPS 是 一 款 将 面 向 对 象 概 念 贯 彻 于 软 件 开

发非常优秀的工具。

这本书关于什么?

CLIPS 用 户 向 导 是 一 个 介 绍 CLIPS 的 基 本 特 征 的 指 南 , 而 不 是 打 算 对

该 工 具 做 一 个 广 泛 的 探 讨 。 本 书 姐 妹 篇 为 CLIPS 参 考 手 册 , 它 提 供 关 于 该

话题的所有广泛的探讨和更多其它内容。

这本书的适用读者群?

CLIPS 用 户 向 导 的 目 的 是 对 专 家 系 统 提 供 简 单 易 懂 的 介 绍 , 适 用 读 者

可能对专家系统一无所知或者知之甚少。

CLIPS 用 户 向 导 可 以 被 用 作 教 材 或 者 自 学 材 料 。 仅 有 的 前 提 是 你 必 须

具 备 高 级 语 言 如 Java, Ada, FORTRAN 或 者 C 的 基 本 知 识 。( 好 的 , 基 本

不是指其他的,但是如果被问起,我们不会在公开场和下承认和取消其声
明 。)

怎样使用这本书?

CLIPS 用 户 向 导 为 那 些 想 亲 身 尝 试 专 家 系 统 编 程 的 人 们 提 供 了 快 速 入

门。例子均具有普遍性。同时,我们知道学习一种新的计算机语言是一个

令人沮丧的体验,因此,本书的写作语言将采用轻快和幽默的风格(我希

望 如 此 ),以 代 替 中 规 中 矩 的 教 科 书 模 式 。但 愿 ,这 种 幽 默 不 会 冒 犯 有 幽 默

感的任何人。

为了最大受益,你最好是在看书的过程中,将书中的实例亲自在文本

中打印出来。在你打印实例的过程中,你将会逐渐明白程序的工作原理和

当你打错时出现的错误提示。案例的结果输出在案例之后给出。最后,当

你 看 完 CLIPS 用 户 向 导 各 章 节 后 , 你 还 应 该 看 看 在 CLIPS 参 考 手 册 中 的 相

关材料。

像 其 他 程 序 语 言 一 样 ,你 只 有 亲 自 写 程 序 才 能 够 真 正 学 好 CLIPS 编 程 。

为 了 真 正 学 会 专 家 系 统 编 程 , 你 应 该 多 在 CLIPS 中 写 程 序 , 并 对 每 个 问 题

抱有兴趣。

感谢

我 十 分 感 谢 那 些 对 本 书 提 出 意 见 和 评 述 的 人 。 谢 谢 Gary Riley, Chris

Culbert,Brian Dantes,Bryan Dulock,Steven Lewis,Ann Baker…( 一 堆 外

国 人 名 )。 特 别 感 谢 Bob Savely 对 CLIPS 改 进 的 支 持 。

• 第一章 事实

如果你忽视事实,你将永远不会担心自己的过错。
本 章 将 对 专 家 系 统 的 基 本 概 念 做 简 单 的 介 绍 。 你 将 会 知 道 在 CLIPS 中

怎 样 插 入 和 移 出 事 实 。 如 果 你 正 在 使 用 的 机 器 是 苹 果 机 或 者 是 IBM( 或 可

兼 容 ) 的 CLIPS 视 窗 版 本 , 那 么 你 可 以 通 过 鼠 标 来 选 择 相 关 的 命 令 来 代 替

输入命令行。键盘上的箭头键也可以移动光标对菜单选项进行选择。

序言

CLIPS 是 一 种 被 用 来 编 写 专 家 系 统 应 用 程 序 的 计 算 机 语 言 。 专 家 系 统

是一组计算机程序,专门用来模仿人类专家的技能和知识。相比之下,一

些普通的程序如报表程序,文本处理器,电子表格,电脑游戏等等,并没

有 包 含 人 类 的 技 能 和 知 识 。( 专 家 的 定 义 之 一 :就 是 某 人 带 着 他 的 公 文 包 在

离 家 50 公 里 之 外 。)

CLIPS 之 所 以 被 称 之 为 专 家 系 统 工 具 , 是 因 为 它 是 一 个 开 发 专 家 系 统

的 完 整 环 境 ,包 括 一 个 整 合 版 本 和 一 个 调 试 工 具 。壳 这 一 词 被 保 留 在 CLIPS

负 责 推 理 的 部 分 中 。 CLIPS 的 壳 提 供 了 专 家 系 统 的 基 本 元 素 :

1. 事实表和实例表:数据的全局存储。

2. 数据库:包括所有的规则和规则表。

3. 推理机:控制所有规则的执行。

CLIPS 的 程 序 一 般 包 含 有 规 则 , 事 实 和 对 象 。 推 理 机 决 定 了 哪 条 规 则

应 该 被 执 行 和 在 什 么 时 候 被 执 行 。 一 个 用 CLIPS 写 成 的 基 于 规 则 库 的 专 家

系 统 程 序 是 一 个 数 据 -驱 动 型 程 序 , 程 序 里 的 事 实 , 对 象 数 据 通 过 推 理 机 的

激活执行。

这 里 有 一 个 例 子 可 以 帮 助 你 知 道 CLIPS 是 如 何 与 其 他 程 序 语 言 如 Java,

Ada,BASIC, FORTRAN 和 C 区 别 开 来 的 。在 程 序 语 言 中 , 执 行 并 不 一 定

需 要 数 据 ,那 是 因 为 在 那 些 语 言 中 的 声 明 已 经 足 够 引 起 执 行 了 。举 例 说 明 ,

在 BASIC 语 言 中 , PRINT 2+2 的 声 明 会 被 立 即 执 行 , 该 声 明 是 一 个 完 整 的

声 明 , 并 不 需 要 额 外 的 数 据 去 驱 动 执 行 。 然 而 , 在 CLIPS 中 , 规 则 的 执 行

必需数据来驱动。
最 初 ,CLIPS 仅 有 表 示 规 则 和 事 实 的 能 力 ,然 而 ,在 6.0 版 本 中 已 经 允

许规则与对象的匹配,与规则与事实匹配一样。同时,通过发送消息来应

用对象不必需要规则了,如果你仅仅只是用对象,那么推理机都可以不需

要 。 在 第 一 章 到 第 七 章 中 , 我 们 将 讨 论 CLIPS 的 事 实 和 规 则 , 八 到 十 二 章

中 包 含 了 CLIPS 的 对 象 特 点 。

开始和结束

你 可 以 在 你 的 系 统 中 输 入 相 应 的 运 行 代 码 来 启 动 CLIPS , 你 将 看 到

CLIPS 的 提 示 如 下 所 示 :

CLIPS>

此 时 ,你 可 以 开 始 在 CLIPS 中 直 接 输 入 命 令 ,这 种 直 接 输 入 命 令 的 方 式 被 称 之

为 最 高 阶 层 。 如 果 你 拥 有 CLIPS 的 图 形 界 面 版 本 ( GUI), 你 也 可 以 用 鼠 标

选 择 相 应 的 菜 单 来 代 替 输 入 命 令 行 。请 参 考 CLIPS GUI 版 本 的 CLIPS 界 面

向导,探讨一下其里面的命令支持。在本书中,为了简约和一致性,我们

假设所有的命令均为输入方式。

离 开 CLIPS 的 一 般 方 式 是 输 入 exit 命 令 , 如 下 :

( exit)

按 照 CLIPS 提 示 点 击 返 回 键 。

建表

与 其 他 编 程 语 言 一 样 , CLIPS 也 有 关 键 字 。 举 个 例 子 , 如 果 你 想 在 事

实 表 中 输 入 数 据 , 你 可 以 使 用 assert 命 令 。
作 为 一 个 assert 实 例 , 在 CLIPS 提 示 后 面 正 确 输 入 下 面 的 命 令 :

CLIPS>(assert (duck))

这 里 ,assert 命 令 以 (duck)作 为 其 参 数 。记 住 点 击 回 车 键 将 命 令 行 发 送 到 CLIPS。

你将看到如下响应:

<Fact-1>

这 表 示 CLIPS 已 经 存 储 了 duck 的 事 实 , 并 将 其 标 识 为 1。 在 CLIPS 中 , 尖 括

弧 被 用 来 作 为 条 目 的 分 隔 符 。 CLIPS 会 自 动 的 增 加 事 实 的 编 号 , 随 着 一 个

或 更 多 的 事 实 被 添 加 , 从 最 高 事 实 -索 引 进 行 列 表 。

注 意 (assert)和 它 的 参 数 (duck)均 用 圆 括 弧 括 住 ,像 其 他 一 些 专 家 系 统 语

言 一 样 ,CLIPS 采 用 LISP 式 样 语 法 ,用 圆 括 弧 作 为 分 隔 符 。虽 然 CLIPS 并

不 是 采 用 LISP 语 言 编 写 , 但 是 LISP 影 响 了 CLIPS 的 发 展 。

检查两遍

假 设 你 想 查 看 一 下 事 实 表 中 的 内 容 , 如 果 你 的 CLIPS 支 持 GUI, 你 便

可以在菜单中选择相应的命令,或者,你还可以通过键盘键入相应的命令

行。接下来,我们将来描述一下键盘命令。

查 看 事 实 库 的 键 盘 命 令 是 facts 命 令 。 在 CLIPS 提 示 后 输 入 (facts),

CLIPS 响 应 后 会 将 事 实 表 列 出 。一 定 记 得 将 命 令 用 圆 括 弧 括 住 ,否 则 CLIPS

会 不 识 别 。 在 该 实 例 中 , (facts)命 令 的 句 法 如 下 :

CLIPS>(facts)

f-0 (initial-fact)

f-1 (duck)
For a total of 2 facts.

CLIPS>

f-0 和 f-1 为 CLIPS 对 事 实 分 配 的 事 实 标 识 。每 个 事 实 被 添 加 进 CLIPS,

被 分 配 唯 一 的 事 实 标 识 ,以“ f”开 头 ,后 面 的 数 字 为 事 实 索 引 。当 启 动 CLIPS,

输 入 如 clear 或 reset( 随 后 有 详 细 的 探 讨 )后 ,事 实 索 引 将 会 被 归 零 ,然 后

随 着 每 个 事 实 的 添 加( assert)逐 步 加 一 。( clear)和 (reset)命 令 同 时 增 加 一

个 (initial-fact)事 实 ,编 号 为 f-0。在 CLIPS 的 早 期 版 本 中 ,该 事 实 被 CLIPS

隐 式 用 来 初 始 化 一 些 规 则 和 被 用 户 显 式 调 用 来 使 事 实 库 初 始 化 ,但 是 现 在 ,

该事实仅被用来提供向后兼容性。

如 果 你 将 duck 在 事 实 表 中 输 入 两 次 ,将 会 出 现 什 么 结 果 呢 ? 让 我 们 试

试 看 , 增 加 一 个 新 事 实 ( duck), 然 后 调 用 ( facts) 命 令 如 下 所 示 :

CLIPS>(assert (duck))

FALSE

CLIPS>(facts)

f-0 (initial-fact)

f-1 (duck)

For a total of 2 facts.

CLIPS>

CLIPS 返 回 FALSE 消 息 , 表 示 不 可 能 执 行 该 条 命 令 , 且 你 将 只 能 见 到

原始的事实:
“ f-1 (duck)”。这 说 明 CLIPS 不 能 接 受 事 实 的 复 制 输 入 。然 而 ,

CLIPS 中 还 有 一 个 超 越 命 令 : set-fact-duplication, 该 命 令 允 许 事 实 的 重 复

输入。

当 然 ,你 可 以 输 入 其 他 不 同 的 事 实 。举 个 例 子 ,增 加 一 个 (quack)事 实 ,

然 后 运 行 (facts)命 令 , 如 下 :
CLIPS>(assert (quack))

<fact-2>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (duck)

f-2 (quack)

For a total of 3 facts.

CLIPS>

注 意 ,( quack) 事 实 已 经 被 添 加 到 事 实 表 中 了 。

事实也会被移出和撤销。当一个事实被撤销,其他的事实索引不会改

变 ,因 此 会 出 现 事 实 索 引 的“ 丢 失 ”。类 似 于 一 个 足 球 运 动 员 离 开 球 队 如 果

没有被补充,其他队员的号码不会因为缺失号码而发生调整(除非他们非

常 讨 厌 这 个 离 队 的 家 伙 , 想 要 忘 掉 他 曾 在 队 中 效 力 过 )。

• 清除所有事实

Clear 命 令 将 所 有 的 事 实 从 内 存 中 移 出 , 代 码 如 下 所 示 :

CLIPS>(facts)

f-0 (initial-fact)

f-1 (duck)

f-2 (quack)

For a total of 3 facts.

CLIPS>(clear)

CLIPS>
事实表中的所有事实被清除。

( clear) 命 令 实 质 上 将 CLIPS 恢 复 到 起 始 启 动 状 态 , 它 清 除 了 CLIPS

的 内 存 空 间 , 重 置 事 实 标 识 为 0 和 增 加 了 一 个 (initial-fact) 事 实 。 增 加

(animal-is duck)事 实 , 然 后 查 看 事 实 表 , 会 发 现 (animal-is duck)的 事 实 标 识

为 f-1, 这 是 因 为 (clear)命 令 重 置 了 事 实 表 的 标 识 。 该 命 令 事 实 上 并 不 只 是

起清除所有事实的作用,除此之外,它还清除所有的规则,在下一章中你

就会看到。

下 面 的 实 例 显 示 了 怎 样 将 三 个 事 实 加 入 到 事 实 表 , 并 用 (facts)命 令 查

看 , 然 后 (clear)命 令 将 这 三 个 事 实 从 内 存 中 清 除 并 重 置 事 实 标 识 为 f-0。

CLIPS>(clear)

CLIPS>(assert (a) (b) (c))

<Facts-3>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (a)

f-2 (b)

f-3 (c)

For a total of 4 facts.

CLIPS>(facts 0)

f-0 (initial-fact)

f-1 (a)

f-2 (b)

f-3 (c)

For a total of 4 facts.

CLIPS>(facts 1)

f-1 (a)
f-2 (b)

f-3 (c)

For a total of 3 facts.

CLIPS>(facts 2)

f-2 (b)

f-3 (c)

For a total of 2 facts.

CLIPS>(facts 1 2)

f-0 (initial-fact)

f-1 (a)

f-2 (b)

For a total of 2 facts.

CLIPS>(facts 1 3 2)

f-0 (initial-fact)

f-1 (a)

f-2 (b)

For a total of 2 facts.

CLIPS>

注 意 , 仅 用 一 个 (assert)便 可 以 增 加 三 个 事 实 : (a),(b)和 (c)。 最 高 索 引 为 3, 通

过 CLIPS 的 信 息 消 息 <Fact-3>返 回 。也 可 以 用 每 个 命 令 增 加 一 个 事 实 的 方 式

( 那 些 这 样 做 的 人 也 许 是 为 了 炫 耀 他 们 的 打 字 速 度 )。

注 :( facts) 命 令 的 完 整 语 法 为 : (facts [<start> [<end> [<maximum>]]]),

<start> 表 示 显 示 索 引 号 大 于 等 于 <start> 的 事 实 , <end> 表 示 小 于 等 于

<end>的 事 实 ,<maximum>表 示 显 示 在 <start>和 <end>之 间 最 多 <maximum>个

事实。
敏感字段和详解

事 实 (duck)和 (quack)被 称 之 为 单 字 段 。一 个 字 段 就 是 一 个 占 位 符( 命 名

或 未 命 名 ),通 常 拥 有 一 个 值 。一 个 简 单 的 类 比 ,你 可 以 将 字 段 想 像 成 一 幅

画框,这个画框能够装载一幅画,也许画中是你的宠物鸭(也许你会好奇

怎 样 用 一 幅 画 表 现“ quack”,有 两 个 法 子 :
( 1)是 弄 一 个 示 波 器 来 显 示 一 只

鸭 子 说 “ quack” 的 波 形 图 , 信 号 的 输 入 来 源 于 一 个 麦 克 风 ;( 2) 对 于 那 些

有 科 学 主 义 倾 向 的 人 , 也 许 还 得 对 “ quack” 信 号 做 一 个 傅 立 叶 变 换 ;( 3)

电 视 里 那 些 叫 卖 神 奇 的 祛 皱 , 减 肥 广 告 。 等 等 )。 只 有 用 deftemplates 才 叫

做占位符,将在第五章中进行详细的介绍。

注 : 这 里 的 ( 3) 提 到 的 电 视 广 告 , 意 思 是 电 视 广 告 里 的 广 告 者 会 大 呼 小 叫 的

对他们的产品爆发欢呼,声音像鸭子叫一样,讽刺幽默。

(duck)事 实 是 一 个 单 独 ,未 命 名 占 位 符 的 事 实 ,值 为 duck。下 面 有 一 个

关于单字段事实的例子,一个字段即是一个值的占位符。类比想像一下字

段 , 就 像 碟 子 (字 段 )盛 食 物 (值 )一 样 的 道 理 。

未命名字段的顺序非常重要。举例,如果一个事实被定义为:

(Brian duck)

表 示 一 个 叫 Brian 的 猎 人 射 杀 了 一 只 鸭 子 , 那 么 事 实 :

(duck Brian)

则 表 示 鸭 子 猎 手 射 杀 了 一 个 叫 Brian 的 猎 人 。 与 之 相 比 , 命 名 字 段 的 顺 序 是 不

重 要 的 , 稍 后 你 将 在 deftemplate 中 看 到 。

事实上,一个好的软件工程应该采用关系型表示法来表述字段,一个

好的事实表示如下:
(hunter-game duck Brian)

表示第一个字段代表猎人,第二个字段代表游戏名称。

现在,一些定义是必需的了。一个表是一组无内在序列的项目集合。

之所以称一个表为有序的,意味着表中的位置是非常重要的。一个多字段

是 有 序 字 段 ,每 个 字 段 都 有 一 个 值 ,特 殊 符 号 nil 意 思 是 无 ,通 常 作 为 一 个

占位符用在空字段中。举例如下:

(duck nil)

可以表示猎人的捕鸭袋中今天一无所获。

注 意 , nil 表 示 了 一 个 占 位 符 , 虽 然 它 没 有 值 。 举 例 , 试 想 一 个 字 段 就

是 一 个 邮 箱 , 没 有 邮 箱 和 邮 箱 中 没 有 信 件 是 完 全 两 码 事 。 如 果 没 有 nil, 这

个 事 实 就 是 一 个 单 字 段 事 实 (duck),如 果 一 个 规 则 依 赖 于 两 字 段 激 活 , 则 该

单字段事实不会被激活,稍后你会看到的。

这 里 有 许 多 不 同 有 效 的 字 段 类 型 : float , integer , symbol , string ,

external-address,fact-address,instance-name 和 instance-address。这 些 字 段

类 型 用 来 存 储 字 段 值 的 类 型 。未 命 名 的 字 段 中 ,值 的 类 型 由 你 的 输 入 决 定 。

在 deftemplates 中 ,你 可 以 显 式 的 声 明 字 段 所 包 含 值 的 类 型 。显 式 的 声 明 加

强了软件工程的概念,是产生一个高效软件的编程训练。

Symbol 是 一 类 字 段 类 型 ,该 类 型 起 始 于 一 个 可 印 刷 的 ASCII 码 并 被 选

择 性 的 加 一 个 0 或 更 多 的 可 印 刷 字 符 。字 段 由 空 格 或 占 位 符 被 普 通 的 分 隔 。

举例:

(duck-shot Brian Gary Rey)


有 四 个 字 段 , 指 示 了 所 有 的 杀 鸭 猎 人 。在 这 个 事 实 中 ,字 段 被 空 格 分 隔 ,并 由

圆括弧括起来。

事实中不能嵌入其他的事实。举例,下面即是一个非法的事实:

(duck (shot Brian Gary Rey))

然 而 ,如 果“ shot”被 当 作 一 个 字 段 名 ,上 面 的 事 实 可 能 是 一 个 合 法 的 deftemplate

事实。后面的三个人名为该字段下的值。

CLIPS 区 分 大 小 写 。 同 样 , CLIPS 中 特 定 的 符 号 有 特 殊 的 意 义 。

‘’ ( )& |< ~ ;? $

“ &”,“ |” 和 “ ~ ” 不 会 独 立 的 使 用 或 作 为 符 号 的 任 何 部 分 。

一些字符的作用等同于分隔符以结束一个符号。下面的字符的作用等

同于分隔符号。

 所 有 的 不 可 印 刷 的 ASCII 码 ,包 括 空 格 ,回 车 键 ,制 表 键 和 换 行

键。

 双 引 号 ,“ ”

 起 始 和 结 束 圆 括 号 ,( )

 &号

 竖线,|

 小 于 , <.这 也 是 尖 括 号 的 一 部 分 。

 波浪字符,~

 分 号 ,; 指 示 一 个 注 释 的 开 始 , 回 车 键 结 束

 ? 和 $?也 许 不 能 作 为 一 个 符 号 的 开 始 , 但 是 可 以 插 入 其 中

分 号 在 CLIPS 的 作 用 是 指 示 一 个 注 释 的 开 始 , 如 果 你 试 图 增 加 一 个 分

号 , CLIPS 便 会 认 为 你 在 输 入 一 段 注 释 并 等 待 你 的 完 成 。 如 果 你 在 最 高 阶
层 (top-level) 中 不 经 意 的 输 入 了 一 个 分 号 , 那 么 输 入 一 个 圆 括 号 的 结 束 部

分 :)并 回 车 。CLIPS 会 以 一 个 错 误 消 息 响 应 并 提 示 给 你 ( 就 像 生 活 中 的 某

些 时 候 , 你 得 做 些 错 误 的 事 情 以 使 得 某 些 事 情 正 确 )。

随 着 你 通 读 这 本 手 册 ,你 将 会 逐 渐 明 白 上 面 那 些 符 号 的 意 义 。除 了“ &”,

“ |”和“ ~”之 外 ,你 将 使 用 其 他 的 表 示 符 号 ,然 而 ,也 许 对 于 有 些 人 ,在

读程序和试图理解程序运行机理时有些困惑。通常情况下,最好是避免使

用这些符号来表示字符,除非你有更好的理由需 要用到它们。

下面是这些符号的一些例子:

duck

duck1

duck_soup

duck-soup

duck1-1_soup-soup

d!?#%^

第 二 类 类 型 的 字 段 是 string。一 个 字 符 串 必 须 用 双 引 号 引 起 来 ,双 引 号

是字段的一部分。引号中可以有 0 个或多个字符。一些例子如下:

“ duck”

“ duck1”

“ duck/soup”

“ duck soup”

“ duck soup is good!!!”

• 第三和第四种字段类型为数字型字段。该字段用来表示整型或浮

点 型 字 段 。 浮 点 型 通 常 被 简 化 为 float。( float-point->float)

CLIPS 中 的 数 字 均 为“ long long”整 型 或 双 精 度 浮 点 型 。没 有 小 数 点 的


数 字 即 是 整 型 ,除 非 它 们 不 属 于 整 型 范 围 。整 型 的 范 围 由 数 字 的 位 数 决 定 ,

N, 用 来 表 示 整 型 如 下 所 示 :

-2 N-1 … 2 N-1 -1

对 于 64 位 机 器 “ long long” 整 型 , 符 合 该 范 围 的 数 字 为 :

-9,223,372,036,854,775,808 … 9,223,372,036,854,775,

807

下面给出一些数字的例子,增加下面的数据到事实中,最后一个数字

为 指 数 表 示 法 , 用 “ e” 或 “ E” 代 替 乘 以 10。

CLIPS>(clear)

CLIPS>(facts)

f-0 (initial-fact)

For a total of 1 fact.

CLIPS>(assert (number 1))

<Fact-1>

CLIPS>(assert (x 1.5))

<Fact-2>

CLIPS>(assert (y -1))

<Fact-3>

CLIPS>(assert (z 65))

<Fact-4>

CLIPS>(assert (distance 3.5e5))

<Fact-5>
CLIPS>(assert (coordinates 1 2 3))

<Fact-6>

CLIPS>(assert (coordinates 1 3 2))

<Fact-7>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (number 1)

f-2 (x 1.5)

f-3 (y -1)

f-4 (z 65)

f-5 (distance 350000.0)

f-6 (coordinates 1 2 3)

f-7 (coordinates 1 3 2)

For a total of 8 facts.

CLIPS>

如 你 所 见 ,CLIPS 将 输 入 的 指 数 表 示 法 转 换 成 数 字 350000.0, 这 是 因 为 当 数 字

足够小,就会被从指数表示转换到浮点型格式。

注 意 上 面 的 每 个 数 字 前 面 都 有 一 个 符 号 开 头 ,如“ number”,“ x”,“ y”

等 。在 CLIPS6.0 版 本 以 前 ,允 许 仅 一 个 数 字 的 事 实 , 然 而 , 现 在 必 需 一 个

符 号 作 为 第 一 字 段 ,同 时 , CLIPS 的 一 些 专 用 字 段 不 能 用 来 作 为 第 一 字 段 ,

但 是 可 以 用 来 作 为 其 他 字 段 。举 个 例 子 ,专 用 关 键 字 not 用 来 指 代 否 定 模 式 ,

但是不能作为一个事实的第一字段。

一个事实由一个或多个被圆括弧括住的字段组成。为了简单化,我们

在前面七章中将仅仅讨论事实,但也有许多对模式匹配应用于对象做了讨

论 。 例 外 的 是 , 一 些 函 数 如 assert 和 retract 仅 仅 只 能 用 于 事 实 , 而 不 能 用

于对象。对对象相应的处理方法将会在第八到第十二章中讨论。
一个事实可以是有序的,也可能是无序的。所有前面你已经看到的事

实都是有序事实,因为字段的顺序决定了它们的不同。举个例子,注意,

CLIPS 会 自 动 将 包 含 相 同 数 字 “ 1” ,“ 2” 和 “ 3” 的 事 实 区 分 开 。

f-6 (coordinates 1 2 3)

f-7 (coordinates 1 3 2)

有 序 事 实 必 须 用 字 段 对 位 于 其 定 义 的 数 据 。 举 例 说 明 , 有 序 事 实 (duck

Brian)有 两 个 字 段 ,同 样 (Brian duck)也 有 两 个 字 段 ,然 而 ,CLIPS 将 其 看 作

两 个 不 同 的 事 实 ,因 为 有 序 事 实 字 段 的 值 是 不 同 的 。相 反 ,事 实 (duck-Brian)

仅 有 一 个 字 段 , 因 为 有 一 个 “ -” 符 号 将 两 个 值 连 结 。

定 义 模 板 事 实 (Deftemplate facts), 稍 后 会 做 详 细 的 表 述 , 它 是 无 序 的 ,

因为它用命名字段来定义数据。这与在 C 和其他语言中应用结构体一样。

多字段通常被由一个或多个的空格,制表,回车或表格组成的空白隔

离开来。举例说明,输入下面的例子,你将发现每个被存储的事实都是一

样的。

CLIPS>(clear)

CLIPS>(assert (The duck says “ Quack” ))

<Fact-1>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (The duck says “ Quack” )

For a total of 2 facts.

CLIPS>(clear)

CLIPS>(assert (The duck says “ Quack” ))

<Fact-1>
CLIPS>(facts)

f-0 (initial-fact)

f-1 (The duck says “ Quack” )

For a total of 2 facts.

CLIPS>

回车的使用是为增加可读性。在下面的例子中,每个字段后加一个回

车,增加的事实与将字段都写在一行的效果是一样的。

CLIPS>(clear)

CLIPS>(assert (The

duck

says

“ Quack” ))

<Fact-1>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (The duck says “ Quack” )

For a total of 2 facts.

CLIPS>

然而,当你在输入一个字符串的时候,要注意插入回车后的效果,例

子如下:

CLIPS>(assert (The

duck

says
“ Quack

” ))

<Fact-2>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (The duck says “ Quack” )

f-2 (The duck says “ Quack

”)

For a total of 3 facts.

CLIPS>

如你所见,在双引号中插入的回车在字符串输出中会将双引号的后半

部 分 移 到 下 一 行 。 CLIPS 会 认 为 f-1 与 f-2 是 两 个 不 同 的 事 实 , 这 一 点 很 重

要。

同 样 , 我 们 也 注 意 到 CLIPS 会 保 存 事 实 中 字 段 里 的 大 写 和 小 写 字 母 。

也 就 是“ The”中 的“ T”和“ Quack”中 的“ Q”。CLIPS 被 认 为 是 区 分 大 小

写 的 , 因 为 它 将 大 写 和 小 写 字 母 区 别 对 待 。 举 例 说 明 , 增 加 事 实 (duck)和

(Duck),然 后 调 用 (facts)命 令 ,你 会 发 现 CLIPS 增 加 了 两 个 不 同 的 事 实 (duck)

和 (Duck),这 正 是 因 为 CLIPS 是 区 分 大 小 写 的 缘 故 。

下面的例子将更清楚的表现了回车应用于表中,增加可读性的作用。

增加下面的事实,使用空格和回车将字段合适的安排在行中。破折号和减

号 被 使 用 来 创 建 单 字 段 ,这 样 ,CLIPS 就 会 将“ fudge sauce”作 为 一 个 单 字

段了。

CLIPS>(clear)

CLIPS>(assert (grocery-list

ice-cream
cookies

candy

fudge-sauce))

<Fact-1>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (grocery-list ice-cream cookie candy fudge-sauce)

For a total of 2 facts.

CLIPS>

如 你 所 见 , CLIPS 将 回 车 和 制 表 置 换 为 单 空 格 。 当 人 们 在 读 一 段 程 序

时 , 使 用 合 适 的 空 格 会 带 来 许 多 方 便 , CLIPS 会 将 其 自 动 置 换 为 单 空 格 。

• 风格问题

用事实的第一个字段来描述后续字段的关系是很好的基于规则编程风

格 。在 此 风 格 中 ,第 一 个 字 段 被 称 为 关 系 ,事 实 的 剩 余 字 段 被 用 来 指 定 值 。

例 子 :(grocery-list ice-cream coolies candy fudge-sauce)中 破 折 号 用 来 将 多 词

组合成一个单字段。

良 好 的 文 档 处 理 在 专 家 系 统 中 比 其 他 语 言 如 Jave,C,Ada 等 更 显 重 要 ,

这 是 因 为 专 家 系 统 中 的 规 则 并 不 是 普 通 的 按 顺 序 执 行 。 CLIPS 采 用 模 板

(deftemplate)的 意 义 来 描 述 事 实 以 帮 助 程 序 员 编 写 程 序 。

另 一 个 关 联 的 事 实 是 (duck),(horse)和 (cow)。一 个 好 的 提 交 它 们 的 格 式

如下所示:

(animal-is duck)

(animal-is horse)

(animal-is cow)
或采用单事实:

(animals duck horse cow)

通 过 关 系 animal-is 和 animals 来 表 述 它 们 之 间 的 关 系 , 使 得 人 们 在 阅 读 代 码 时

能够一目了然。

一 个 明 确 的 关 联 , animal-is 和 animals, 比 隐 式 的 定 义 (duck), (horse)

和 (cow)能 使 人 们 得 到 更 多 的 信 息 。 这 个 足 够 简 单 的 例 子 让 任 何 人 都 能 断 定

字段间的隐含关系,但当人们在写一个并没有明确关系的事实时,同时也

是一个简单的圈套(事实上,使事情复杂化要比使事情简单化简单很多,

这 是 因 为 人 们 通 常 对 于 复 杂 的 印 象 比 简 单 要 深 刻 许 多 。)

消除空格

之前我们介绍了空格用来分隔多字段,下面我们将看到在事实中,空

格的作用不仅仅如此。举个例子:

CLIPS>(clear)

CLIPS>(assert (animal-is walrus))

<Fact-1>

CLIPS>(assert ( animal-is walrus ))

FALSE

CLIPS>(assert ( animal-is walrus ))

FALSE

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is walrus)


For a total of 2 facts.

CLIPS>

仅 有 一 个 事 实 (animal-is walrus)被 添 加 了 ,CLIPS 忽 视 了 空 格 ,并 认 为 三 个 被 添

加 的 事 实 是 相 同 的 。因 此 ,当 输 入 相 同 的 事 实 时 ,CLIPS 返 回 FALSE。CLIPS

不 允 许 输 入 相 同 的 事 实 , 除 非 你 改 变 set-fact-duplicate 设 置 。

如果你想在事实中包含空格,那么你必须使用双引号,举例如下:

CLIPS>(clear)

CLIPS>(assert (animal-is “ duck” ))

<Fact-1>

CLIPS>(assert (animal-is “ duck ” ))

<Fact-2>

CLIPS>(assert (animal-is “ duck” ))

<Fact-3>

CLIPS>(assert (animal-is “ duck ” ))

<Fact-4>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is “ duck” )

f-2 (animal-is “ duck ” )

f-3 (animal-is “ duck” )

f-4 (animal-is “ duck ” )

For a total of 5 facts.

CLIPS>

注 意 上 面 ,在 CLIPS 中 ,空 格 的 使 用 使 得 每 个 事 实 都 不 同 ,虽 然 在 我 们 看 来 是
同一个事实。

如果你想在字段中包含双引号,该怎么办?正确的方法是使用反斜线

符 号 “ \” 将 双 引 号 插 入 到 事 实 中 , 如 下 面 的 例 子 所 示 :

CLIPS>(clear)

CLIPS>(assert (single-quote “ duck” ))

<Fact-1>

CLIPS(assert (single-quote “\”duck“\”))

<Fact-2>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (single-quote “ duck” )

f-2 (single-quote ““ duck” ”))

For a total of 3 facts.

CLIPS>

撤销事实

现在你已经知道怎么添加一个事实到事实表中,现在是时候学习怎样

撤 销 它 们 了 。 将 事 实 表 中 的 事 实 移 除 称 之 为 撤 销 , 使 用 retract 命 令 。 撤 销

一个事实,你必须指定所撤销事实的索引作为撤销命令的参数,建立你的

事实表如下所示:

CLIPS>(clear)

CLIPS>(assert (animal-is duck))

<Fact-1>

CLIPS>(assert (animal-sound quack))


<Fact-2>

CLIPS>(assert (The duck says “ Quack.” ))

<Fact-3>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is duck)

f-2 (animal-sound quack)

f-3 (The duck says “ Quack.” )

For a total of 4 facts.

CLIPS>

如 果 要 移 除 索 引 为 f-3 的 最 后 一 个 事 实 ,键 入 撤 销 命 令 并 选 择 你 所 要 撤

销的事实,如下所示:

CLIPS>(retract 3)

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is duck)

f-2 (animal-sound quack)

For a total of 3 facts.

CLIPS>

如果你试图移除一个已经被移除的或者根本不存在的事实,将会出现

什么结果?让我们来试试:

CLIPS>(retract 3)

[PRNTUTIL1] Unable to find fact f-3.


CLIPS>

可 以 看 到 , 当 你 试 图 移 除 一 个 不 存 在 的 事 实 时 , CLIPS 会 发 布 一 个 错 误 提 示 。

如果你没有给予,你当然也没有道理拿回什么。

现在,让我们撤销其他的事实,如下所示:

CLIPS>(retract 2)

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is duck)

For a total of 2 facts.

CLIPS>(retract 1)

CLIPS>(facts)

f-0 (initial-fact)

For a total of 1 fact.

CLIPS>

撤销一个事实,你必须指定该事实的索引。

你可以一次撤销多条事实,如下所示:

CLIPS>(clear)

CLIPS>(assert (animal-is duck))

<Fact-1>

CLIPS>(assert (animal-sound quack))

<Fact-2>

CLIPS>(assert (The duck says “ Quack.” ))

<Fact-3>
CLIPS>(retract 1 3)

CLIPS>(facts)

f-0 (initial-fact)

f-2 (animal-sound quack)

For a total of 2 facts.

CLIPS>

撤 销 多 条 事 实 , 只 要 在 retract 命 令 后 跟 上 相 应 的 事 实 索 引 号 即 可 。

你 也 可 以 用 (retract *)撤 销 所 有 的 事 实 , 这 里 的 *指 代 所 有 的 事 实 。

CLIPS>(clear)

CLIPS>(assert (animal-is duck))

<Fact-1>

CLIPS>(assert (animal-sound quack))

<Fact-2>

CLIPS>(assert (The duck says “ Quack.” ))

<Fact-3>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is duck)

f-2 (animal-sound quack)

f-3 (The duck says “ Quack.” )

For a total of 4 facts.

CLIPS>(retract *)

CLIPS>(facts)

CLIPS>
• 监视事实

CLIPS 提 供 了 一 些 帮 助 你 调 试 程 序 的 命 令 。 其 中 一 个 命 令 可 以 帮 助 你

连 续 监 视 事 实 (watch facts)的 增 加 和 撤 销 , 这 比 你 总 是 不 断 输 入 (facts)命 令

来查看事实表中的变化要方便得多。

监 视 事 实 是 通 过 输 入 (watch facts)命 令 来 实 现 的 , 如 下 例 子 所 示 :

CLIPS>(clear)

CLIPS>(watch facts)

CLIPS>(assert (animal-is duck))

==>f-1 (animal-is duck)

<Fact-1>

CLIPS>

右 双 箭 头 符 号 ==>表 示 事 实 正 在 被 添 加 到 内 存 中 , 左 双 箭 头 <==表 示 事

实正在从内存中移除,如下所示:

CLIPS>(reset)

<==f-0 (initial-fact)

<==f-1 (animal-is duck)

==>f-0 (initial-fact)

CLIPS>(assert (animal-is duck))

==>f-1 (animal-is duck)

<Fact-1>

CLIPS>(retract 1)

<==f-1 (animal-is duck)

CLIPS>(facts)
f-0 (initial-fact)

For a total of 1 fact.

CLIPS>

(watch facts)命 令 提 供 对 事 实 表 状 态 的 动 态 显 示 , (facts)命 令 显 示 的 是

静 态 的 当 前 事 实 表 中 所 包 含 的 事 实 。 关 闭 监 视 事 实 的 命 令 为 : (unwatch

facts)。

你 可 以 监 视 的 项 目 有 很 多 , 下 面 列 举 出 来 , 在 《 CLIPS 参 考 指 南 》 中

有 详 细 的 表 述 。CLIPS 中 的 注 释 以 分 号 开 始 ,分 号 后 面 的 内 容 将 会 被 CLIPS

忽略。

(watch facts)

(watch instances) ; 应用于对象

(watch slots) ; 应用于对象

(watch rules)

(watch activations)

(watch messages) ; 应用于对象

(watch message-handlers) ; 应用于对象

(watch generic-functions)

(watch methods) ; 应用于对象

(watch deffunctions)

(watch compilations) ; 默 认 的

(watch statistics)

(watch globals)

(watch focus)

(watch all) ; 监视所有项目


随 着 你 使 用 到 CLIPS 的 更 多 功 能 , 你 将 发 现 (watch)命 令 在 调 试 过 程 中

非 常 的 有 用 。通 过 输 入 unwatch 命 令 可 以 关 闭 监 视 (watch)命 令 。举 例 说 明 ,

如 果 要 关 闭 监 视 编 译 , 则 输 入 (unwatch compilations)即 可 。

一点帮助

CLIPS 提 供 有 效 的 在 线 帮 助 。 获 得 帮 助 只 需 输 入 (help)命 令 然 后 回 车 即

可 。 不 久 ,你 将 会 看 到 一 个 细 目 菜 单 。更 多 的 关 于 (help)命 令 的 信 息 ,请 参

考 HELP_USAGE 帮 助 章 节 。退 出 帮 助 的 方 法 是 一 直 按 回 车 键 ,直 到 CLIPS

提 示 出 现 。 如 果 出 现 错 误 消 息 提 示 , 则 表 明 CLIPS 没 有 找 到 帮 助 文 件 :

clips.hlp, 你 可 以 用 (help-path)命 令 来 找 出 CLIPS 该 文 件 的 路 径 。

第二章 规则

如 果 你 想 你 的 生 活 硕 果 累 累 , 那 么 别 打 破 规 则 ---而 是 去 制 定 规 则 !

在前面一章中的学习中,你已经对事实有所了解了。现在你将马上看

到专家系统的规则将怎样利用事实驱动程序执行。

构造良好的规则

完成一项有价值的工作,专家系统必须得有事实和规则。前面你已经

知道了事实的添加和撤销,现在你将了解规则是怎样工作的。一条规则与

程 序 语 言 如 Java, C 或 Ada 中 的 IF THEN 表 述 非 常 相 似 。 IF THEN 规 则 可

以用自然语言与计算机语言来混合表示,如下所示:

IF certain conditions are true

THEN execute the following actions

上述表述又被称为伪代码,伪代码字面的意思是错误的代码。伪代码
不能被计算机识别和执行,但是它对书写可执行代码提供了有用的指南。

伪 代 码 在 文 档 规 则 中 也 非 常 有 用 。 如 果 你 记 住 IF THEN 的 类 比 特 性 , 那 么

将 规 则 从 自 然 语 言 转 化 到 CLIPS 语 言 将 很 简 单 。随 着 你 CLIPS 实 践 的 增 加 ,

你 将 发 现 在 CLIPS 中 写 规 则 非 常 的 简 单 。你 可 以 在 CLIPS 中 直 接 输 入 规 则 ,

也 可 以 新 建 一 个 文 本 文 件 , 将 规 则 写 在 里 面 , 然 后 加 载 到 CLIPS 中 来 。

关于鸭子叫声规则的伪代码可以写成如下形式:

IF the animal is a duck

THEN the sound made is quack

下 面 是 采 用 CLIPS 语 法 将 上 面 的 伪 代 码 写 成 一 个 事 实 和 一 个 命 名 为

duck 的 事 实 。规 则 名 紧 跟 在 关 键 字 defrule 后 面 。虽 然 你 可 以 将 规 则 都 写 在

一行里面,但是我们通常将规则分成几段放在几行里书写,便于程序的阅

读和编辑。

CLIPS>(unwatch facts)

CLIPS>(clear)

CLIPS>(assert (animal-is duck))

<Fact-1>

CLIPS>(defrule duck

(animal-is duck)

=>

(assert (sound-is quack)))

CLIPS>

如 果 你 按 照 上 面 正 确 的 输 入 ,你 便 会 看 到 CLIPS 的 提 示 符 出 现 ,否 则 ,

你将会看到一个错误消息提示。如果你得到一个错误消息,也许是你拼错
了关键字或你遗漏了圆括号。记住,在一个声明中,圆括弧的左边和右边

部分的数目是配套的。

下面将给出一个相同的规则,该规则中增加了对规则每部分的注释。

同 时 也 增 加 了 可 选 的 规 则 头 (rule-header)注 释 :“ Here comes the quack”。 规

则中只能包含一个规则头注释,且必须写在规则名之后和第一个模式

(pattern)之 前 。虽 然 现 在 我 们 只 是 讨 论 基 于 事 实 的 模 式 匹 配 ,一 般 来 说 ,模

式 的 匹 配 时 基 于 模 式 实 体 上 (pattern entity)的 。 模 式 实 体 是 一 个 事 实 , 也 可

以是一个用户定义类的实例。基于对象的模式匹配将稍后讨论。

CLIPS 基 于 模 式 实 体 来 进 行 模 式 匹 配 。 当 然 , 由 空 格 , 制 表 和 回 车 组

成的空格将规则的几个部分分隔开来,以增强可读性。其他的注释由分号

引 导 , 直 到 按 下 回 车 键 结 束 一 行 。 CLIPS 忽 略 注 释 里 的 内 容 。

(defrule duck “ Here comes the quack” ; 规则头

(animal-is duck) ; 模式

=> ; THEN 箭 头

(assert (sound-is quack))) ; 执行

 CLIPS 中 , 同 时 刻 只 能 仅 有 一 个 规 则 名 存 在 。

• 输 入 同 一 个 规 则 名 ,如 本 例 中 的“ duck”,将 会 更 替 前 面 规 则 名 为

“ duck”里 已 经 存 在 的 一 切 。也 就 是 说 ,CLIPS 中 可 能 有 许 多 条 规 则 ,但 是

只 能 有 一 条 被 命 名 为 “ duck” 的 规 则 。 这 与 其 他 程 序 语 言 中 一 个 程 序 名 只

能标识唯一程序段是一样的道理。

规则的常规语法如下所示:

(defrule rule_name “ optinal_comment”

(pattern_1) ; 由 一 些 在“ =>”之 前 的 元 素 组 成 的 规 则 左 部

(pattern_2)
.

(pattern_N)

=>

(action_1) ; 由 一 些 在“ =>”之 后 的 元 素 组 成 的 规 则 右 部

(action_2)

(action_M)) ; 最 后 一 个 “ )” 是 与 “ defrule” 前 面 的 “ )” 配

; 套 的 。保 证 你 的 圆 括 弧 完 整 ,否 则 你 将 得 到

错误

; 消息提示

整 个 规 则 必 须 用 圆 括 弧 括 住 ,每 个 模 式 (pattern)和 每 个 行 为 (action)都 必

须 用 圆 括 弧 括 住 。行 为 通 常 是 一 类 没 有 返 回 值 (return value)的 函 数 ,但 是 它

可 以 完 成 一 些 有 用 的 执 行 , 如 (assert)和 (retract)。 举 个 例 子 , 一 个 行 为 可 以

是 (assert (duck))。 这 里 的 函 数 名 是 “ assert”, 它 的 参 数 是 “ duck”。 注 意 ,

我 们 并 没 有 希 望 得 到 一 个 如 数 字 型 的 返 回 值 ,而 是 使 得 事 实 (duck)被 增 加 到

CLIPS 中 去 。 CLIPS 中 的 函 数 (function)是 一 段 可 执 行 代 码 , 该 段 代 码 被 特

定 的 函 数 名 标 识 , 返 回 有 用 的 值 或 产 生 有 用 的 副 作 用 , 如 (printout)。

一个规则通常包含有多个模式和行为。模式和行为的数量并不一定得

相等,这就是上面例子中用 N 和 M 来代指的意义。

零个或多个模式写在规则名之后。每个模式包含一个或多个字段。在

上 面 的 duck 规 则 中 ,模 式 为 (animal-is duck),字 段 为“ animal-is”和“ duck”。

CLIPS 试 图 将 模 式 与 事 实 表 中 的 事 实 进 行 匹 配 , 如 果 规 则 的 模 式 与 事 实 匹
配 成 功 , 规 则 将 会 被 激 活 (activated)而 放 入 到 议 程 (agenda)中 。 议 程 中 存 放

的是所有被激活的规则集合。议程中通常包含零个或多个激活的规则。

规 则 中 ,模 式 后 面 的 符 号“ =>”被 称 之 为 箭 号 (arrow),箭 号 是 IF-THEN

规 则 的 THEN 部 分 开 始 的 标 记 ( 也 许 可 以 被 读 作 “ 意 味 着 ”)。

规 则 的 最 后 部 分 为 零 个 或 多 个 行 为 , 当 规 则 被 触 发 (fire)时 , 这 些 行 为

将 会 被 执 行 。在 我 们 的 实 例 中 ,行 为 是 增 加 一 个 事 实 (sound-is quack)。 Fire

一 词 意 味 着 CLIPS 已 经 选 定 了 议 程 中 某 条 规 则 并 执 行 。

 当议程中没有激活的规则时,程序停止执行。

当 议 程 中 有 多 条 激 活 规 则 时 , CLIPS 自 动 决 定 哪 条 规 则 将 被 合 理 的 触

发 。 CLIPS 依 照 增 加 优 先 权 和 特 权 (salience)来 对 议 程 的 激 活 排 序 。

规 则 中 箭 号 之 前 的 被 称 之 为 左 部 (LHS),箭 号 之 后 的 部 分 被 称 之 为 右 部

(RHS)。如 果 没 有 指 定 模 式 ,则 CLIPS 会 在 输 入 (reset)命 令 后 自 动 的 激 活 该

条规则。

让鸭子叫吧

CLIPS 通 常 会 执 行 议 程 中 最 高 优 先 权 规 则 右 部 的 行 为 部 分 。 随 后 该 条

规则将会被移出议程,接下来最高特权规则的行为将会被执行。这样持续

执行下去,直到议程中没有激活的规则或输入了停止激活的命令为止。

你 可 以 通 过 议 程 (agenda)命 令 来 查 看 议 程 中 的 内 容 , 举 例 说 明 :

CLIPS>(agenda)

0 duck: f-1

For a total of 1 activation.

CLIPS>

第 一 个 数 字 “ 0” 表 示 规 则 “ duck” 的 激 活 特 权 值 ,“ f-1” 为 事 实 的 标

识 , (animal-is duck)为 匹 配 激 活 。 如 果 没 有 显 式 的 声 明 特 权 值 , 则 CLIPS


默 认 为 0。 特 权 值 的 范 围 为 -10000 到 10000。 本 书 中 , 我 们 将 用 default 的

定义来作为标准方式。

如 果 议 程 中 仅 有 一 个 规 则 , 该 规 则 将 被 触 发 。 前 面 知 道 了 duck-sound

规则的模式左部为:

(animal-is duck)

该 模 式 刚 好 与 (animal-is duck)事 实 符 合 , 因 此 duck-sound 规 则 将 会 被 触 发 。

模 式 的 字 段 被 称 之 为 字 面 约 束 (literal constraint)。之 所 以 称 之 为 字 面 意

味着有一个常数值,与之对立的是值可以改变的变量。在此例中,字面为

“ animal-is” 和 “ duck”。

输 入 run 命 令 即 可 使 程 序 运 行 。敲 入 (run)并 回 车 ,然 后 输 入 (facts)命 令

查看通过该规则有哪些事实被添加。

CLIPS>(run)

CLIPS>(facts)

f-0 (initial-fact)

f-1 (animal-is duck)

f-2 (sound-is quack)

For a total of 3 facts.

CLIPS>

在 操 作 之 前 ,让 我 们 使 用 save 命 令 来 保 存 duck 规 则 ,这 样 你 就 可 以 避

免 重 复 输 入 了 ( 如 果 你 还 没 有 将 这 些 保 存 到 编 辑 器 中 )。 输 入 命 令 如 下 :

(save “ duck.clp” )
将 CLIPS 内 存 中 的 规 则 保 存 到 命 名 为 “ duck.clp” 的 文 件 中 ,“ .clp” 是 一 个 简

单 方 便 的 扩 展 名 ,让 我 们 方 便 知 道 这 是 一 个 CLIPS 的 源 文 件 。注 意 ,从 CLIPS

内存中保存下的代码只保留了双引号内可选规则头的注释,而分号后的注

释就没有了。

踢你的鸭子

也 许 此 时 你 会 有 一 个 有 趣 的 问 题 , 如 果 重 复 执 行 (run), 结 果 会 这 样 ?

当 一 个 规 则 被 事 实 满 足 时 ,该 规 则 会 被 触 发 ,然 而 ,如 果 你 重 复 执 行 (run),

你会发现该条规则不将被触发了。这也许让人有一点沮丧,然而,在你做

出 一 些 极 端 的 减 轻 沮 丧 的 事 情 之 前 ---如 狠 踢 你 的 宠 物 鸭 ---你 得 多 了 解 一 些

专家系统的基本原理。

当规则的模式与下面的几点匹配时,规则被激活:

1. 之前不存在的不同的新的模式实体或

2. 该 模 式 实 体 存 在 ,但 是 被 撤 销 或 者 被 重 新 添 加 了 。举 个 例 子 ,旧 模

式实体的副本便是一个新的模式实体。

规则和匹配的模式目录,都是被激活的。如果是规则或模式实体,或

者同时被改变了,激活将会被移除。一个激活的也可以通过命令或另一规

则的行为被移除,该规则在移除激活的先决条件前被触发。

推理机通过特权值将激活进行分类。这种分类过程被称之为冲突消解

(conflict resolution), 因 为 它 消 解 了 决 定 下 一 个 触 发 规 则 的 冲 突 。 CLIPS 依

照议程中最高的特权值进行规则的激活,并移除激活。这种执行被称之为

触发,就像神经细胞的激活。当有适当的刺激时,神经细胞会激发出一定

的 电 压 脉 冲 ,神 经 细 胞 激 活 后 ,将 遭 受 折 射 (refraction)并 在 一 定 时 期 内 不 能

被再次触发。如果没有折射,神经细胞将会在刺激作用下无休止的被激活

下去。

如 果 没 有 折 射 效 应 ,专 家 系 统 将 会 经 常 陷 入 到 无 关 重 要 的 循 环 当 中 去 。

因为,一旦规则被触发,那么它将在相同的事实作用下无休止的被触发下
去。在现实世界中,引起触发的刺激最终都会消失。举个例子,一只真的

鸭子也许会游走或在电影里充当一个角色,然而,在计算机世界里,一旦

数据被存储,它将一直保存在那儿,除非有外部声明移除或电脑断电。

下 面 的 例 子 展 示 了 一 个 规 则 的 激 活 和 触 发 。 注 意 (watch)命 令 被 用 来 更

好的显示每个事实和激活。右箭号表明激活和事实正在被添加,左箭号表

明已存在的事实和激活。

CLIPS>(clear)

CLIPS>(defrule duck

(animal-is duck)

=>

(assert (sound-is quack)))

CLIPS>(watch facts)

CLIPS>(watch activations)

CLIPS>(assert (animal-is duck))

==>f-1 (animal-is duck)

==>Activation 0 duck:f-1 ; 激 活 的 默 认 权 值 为 0, 其 后 是 规 则 名 : 模 式

<Fact-1> ; 实体索引

CLIPS>(assert (animal-is duck)) ; 注意复制的事实不会被输入

FALSE

CLIPS>(agenda)

0 duck: f-1

For a total of 1 activation.

CLIPS>(run)

==>f-2 (sound-is quack)

CLIPS>(agenda) ; 当规则被触发后,议程为空

CLIPS>(facts)
f-0 (initial-fact) ; 即使事实已与规则匹配,折射也不会允许该激

f-1 (animal-is duck) ; 活,因为该规则等待事实的激活

f-2 (sound-is quack)

For a total of 3 facts.

CLIPS>(run)

CLIPS>

你也可以撤销事实然后又重新添加作为新的事实来让规则重复触发。

• 查看规则

在 你 运 行 CLIPS 时 , 也 许 你 想 查 看 某 一 条 规 则 , 这 里 有 一 个 命 令 :

ppdefrule---恰 当 的 打 印 规 则 ---打 印 一 条 规 则 。 查 看 某 条 规 则 , 则 指 定 其 规

则 名 为 ppdefrule 的 参 数 即 可 , 举 例 如 下 :

CLIPS>(ppdefrule duck)

(defrule MAIN::duck

(animal-is duck)

=>

(assert (sound-is quack)))

CLIPS>

为 了 增 加 可 读 性 , CLIPS 将 规 则 的 不 同 部 分 分 布 在 不 同 的 行 中 。 规 则

箭 号 之 前 的 模 式 部 分 仍 然 被 称 之 为 LHS, 箭 号 之 后 的 行 为 部 分 仍 然 被 称 之

为 RHS。 术 语 MAIN 引 用 MAIN 模 块 表 明 该 条 规 则 是 自 定 义 的 。 你 可 以 定

义模块,将规则与那些可以被其他编程语言不同包装,模块,过程或函数

纳入的声明类比。模块的使用使得编写那些有许多条规则的专家系统变得

简单,这样,对于每个模块,它们大多在自己的议程中整合在一起了。如
果 你 想 了 解 更 多 , 请 参 考 CLIPS 参 考 指 南 。

如果你想打印一条规则,而你又忘掉了该规则的规则名,该怎么办?

不 用 慌 ,你 可 以 在 CLIPS 提 示 符 后 面 使 用 rules 命 令 来 打 印 出 所 有 的 规 则 名 ,

举例如下:

CLIPS>(rules)

Duck

For a total of 1 defrule.

CLIPS>

给我写信

规 则 的 RHS 部 分 除 了 添 加 一 条 新 规 则 , 你 还 可 以 使 用 printout 函 数 打

印 出 相 应 的 信 息 。 同 样 , CLIPS 有 回 车 换 行 关 键 字 : crlf, 该 关 键 字 以 换 行

格 式 来 改 进 输 出 效 果 。有 一 点 小 改 变 就 是 ,crlf 不 被 圆 括 弧 包 含 。举 例 如 下 :

CLIPS>(defrule duck

(animal-is duck)

=>

(printout t “ quack” crlf)) ; 一 定 要 打 出 “ t”

==>Activation 0 duck:f-1

CLIPS>(run)

quack

CLIPS>

双 引 号 内 的 文 本 即 为 输 出 。一 定 记 得 在 printout 命 令 后 输 入“ t”,这 将

告 知 CLIPS 将 结 果 输 出 到 电 脑 的 标 准 输 出 设 备 (standard output device)中 。


通 常 , 标 准 输 出 设 备 是 你 电 脑 的 终 端 (terminal)(因 此 在 printout 后 面 接 字 母

“ t”)。然 而 ,这 可 能 会 被 重 新 定 义 ,这 样 标 准 输 出 设 备 也 可 能 是 其 他 的 设

备,如调制解调器或磁盘。

其他特性

declare salience 命 令 提 供 对 增 添 到 议 程 中 的 规 则 的 外 部 控 制 。在 使 用 该

特性的时候要注意不要太过于自由以免你的程序被人为控制太多。

set-incremental-reset 命 令 禁 止 在 规 则 被 输 入 之 前 查 看 该 规 则 的 事 实 。获 取 增

加 的 重 置 值 命 令 为 :get-incremental-reset。让 一 条 规 则 重 复 触 发 的 一 个 办 法

是 使 用 refresh 规 则 命 令 来 强 制 使 其 重 新 激 活 。

load 命 令 载 入 前 面 你 已 经 保 存 在 磁 盘 中 命 名 为“ duck.clp”文 件 或 者 相

应 文 件 夹 下 的 任 何 文 件 名 里 的 规 则 。 你 还 可 以 使 用 load 命 令 载 入 一 个 包 含

规则的文本文件。

最 快 的 载 入 文 件 的 方 法 是 ,首 先 用 bsave 二 进 制 存 储 命 令 将 规 则 存 储 为

机 器 可 读 二 进 制 格 式 。载 入 的 二 进 制 命 令 为 bload。这 样 , CLIPS 内 存 会 不

加解释的快速读取这些二进制规则。

另外两个有用的命令可以帮助你通过一个文件来保存和载入事实。它

们 是 save-facts 和 load-facts。( save-facts)命 令 将 会 保 存 所 有 事 实 表 中 的 事

实 , (load-facts)命 令 将 会 导 入 文 件 事 实 表 中 的 事 实 。

batch 命 令 允 许 你 像 在 顶 层 输 入 一 样 执 行 一 个 文 件 命 令 。另 外 一 个 有 用

的 命 令 为 你 的 操 作 系 统 提 供 一 个 界 面 。 system 命 令 允 许 操 作 系 统 的 执 行 和

在 CLIPS 内 的 可 执 行 。 如 果 你 想 了 解 更 多 此 类 信 息 , 请 查 阅 CLIPS 参 考 指

南。

第三章 详细资料

问题不是大局,而是细节。
在 前 面 的 两 章 中 , 你 已 经 学 习 了 CLIPS 的 基 础 。 现 在 , 你 将 会 学 到 怎

样结合这些基础构建一个强大的程序。

红绿灯

到目前为止,你还只是看到一些仅包含一条规则的简单程序。然而,

只 包 含 一 条 规 则 的 专 家 系 统 无 疑 作 用 有 限 。实 际 的 专 家 系 统 通 常 包 含 上 百 ,

上千条规则。让我们来看看一个需要多条规则的应用软件程序吧。

假设你想写一个专家系统来决定一个移动式遥控装置如何对交通灯进

行响应,最好是用多条规则去编写这个问题的类型。举个例子,红灯和绿

灯情况下的规则按如下书写:

(defrule red-light

(light red)

=>

(printout t “ Stop” crlf))

(defrule green-light

(light green)

=>

(printout t “ Go” crlf))

当 上 述 规 则 被 输 入 到 CLIPS 后 ,增 加 一 个 (light red) 事 实 并 运 行 ,你

将 会 看 到 “ Stop” 被 打 印 出 来 。 再 增 加 一 个 (light green)事 实 并 运 行 , 你 会

看 到 “ Go” 被 打 印 出 来 。

行路
如果你考虑上面所述,交通灯不光只简单的包含有红灯,绿灯,应该

还是黄灯存在,同时还有绿色的箭头标识来保护左转等。手型的交通灯亮

与灭指示了行人的行与止。行与止的信号取决于我们装置显示是行人还是

行车,这可能要关注一些不同的信号。

行人或行车的信息必须被添加,此外交通灯的状态信息也得添加。规

则必须覆盖所有的情况,但是它们必须有多个模式。举个例子,假设我们

希望当装置状态为行人和行人信号亮时,一个规则被触发,该规则应写成

如下所示:

(defrule take-a-walk

(status walking)

(walk-sign walk)

=>

(printout t “ Go” crlf))

上 面 的 规 则 中 包 含 有 两 个 模 式 ,规 则 的 每 个 模 式 必 须 在 事 实 表 中 有 相 对

应的事实满足才能触发。看看这些怎样工作,输入上面的规则并添加事实

(status walking)和 (walk-sign walk),当 执 行 (run),规 则 的 模 式 均 被 满 足 ,程

序 输 出 “ Go”。

你可以在一条规则中加入多条模式或行为。重要的一点是,只有当规

则中所有的模式都被事实表中的事实满足时,规则才能被触发。这种约束

类 型 被 称 为 逻 辑 与 条 件 元 素 ( logical AND conditional element(CE)), 是 关

于 布 尔 型 的 “ 与 ” 关 系 。 AND 关 系 只 有 当 所 有 的 条 件 都 为 真 时 才 为 真 。

因 为 模 式 为 AND 类 型 , 如 果 只 有 一 个 模 式 被 满 足 , 规 则 不 会 被 触 发 。

只 有 给 出 规 则 LHS 中 所 有 的 模 式 满 足 , 规 则 才 能 被 放 入 到 议 程 中 。

• 策略的问题
策 略 (strategy)一 词 最 初 是 一 个 军 事 术 语 , 用 在 战 争 的 谋 划 和 行 动 中 。

现在,该术语普遍被用在商海(商海即是战场)中,适用于一个组织为了

达到他们的目的所做的高级计划等。
“比世界上其他的人卖出更多的多脂汉

堡 , 赚 更 多 的 钱 !”

在 专 家 系 统 中 ,strategy 术 语 的 一 个 用 法 是 激 活 的 冲 突 消 解 。那 么 你 也

许 会 说 ,“ 那 好 ,我 现 在 就 将 设 计 好 我 的 专 家 系 统 ,以 便 同 一 时 刻 仅 有 一 条

规 则 可 能 被 激 活 , 那 么 就 用 不 上 冲 突 消 解 了 。” 好 消 息 是 : 如 果 你 成 功 了 ,

那么冲突消解确实无关紧要,坏消息是:你的成功证明了你的应用软件能

被 一 个 连 续 的 程 序 很 好 的 表 达 出 来 ,那 么 你 还 不 如 首 选 在 C,Java 或 者 Ada

中编写代码,犯不着去编写一个专家系统。

CLIPS 提 供 了 七 种 不 同 的 冲 突 消 解 策 略 : 深 度 优 先 (depth), 广 度 优 先

(breadth), LEX, MEA, complexity, simplicity 和 随 机 (random)。 在 不 考 虑

具体的应用软件程序时,很难说清哪一种策略更好。即使那样,判断上面

的七种策略哪一个是“最好”的,也相当困难。如果你想了解更多关于这

些 策 略 的 详 细 信 息 , 请 参 考 CLIPS 参 考 指 南 。

深 度 优 先 策 略 (depth strategy)是 CLIPS 标 准 默 认 策 略 (default strategy)。

当 CLIPS 第 一 次 启 动 时 , 该 默 认 设 置 便 会 被 自 动 设 置 , 后 面 , 你 可 以 更 改

默 认 设 置 。在 深 度 优 先 策 略 中 ,在 高 权 值 的 激 活 后 ,同 权 值 或 低 权 值 之 前 ,

新的激活将会被放到议程中。这就是说议程中是从高权值到低权值进行排

序的。

在本书中,所有的讨论和例子均是在假设为深度优先策略前提下的。

现在,你知道了所有的这些可选设置是多么的有用,一定得记住:当

你运行一个由你和其他人共同编写的专家系统时,要保证你们的设置是一

致的。否则,你会发现操作无效或者甚至是错误的。事实上,最好的办法

是 在 你 开 发 的 过 程 中 ,对 任 何 系 统 进 行 显 式 的 设 置 编 码 ,以 保 证 正 确 配 置 。

自定义事实
当 你 使 用 CLIPS 的 时 候 , 你 也 许 会 对 在 顶 层 中 输 入 相 同 的 声 明 事 实 而

感到厌烦。如果你准备在程序运行的时候用到相同的声明,首先你可以用

批处理文件加载磁盘里的声明,其次,你还可以使用自定义事实关键字:

deffacts。 举 例 如 下 :

CLIPS>(unwatch facts)

CLIPS>(unwatch activations)

CLIPS>(clear)

CLIPS>(deffacts walk “ Some facts about walking”

(status walking) ; 被声明的事实

(walk-sign walk)) ; 被声明的事实

CLIPS>(reset) ; 引入被自定义声明的事实

CLIPS>(facts)

f-0 (initial-fact)

f-1 (status walking)

f-2 (walk-sign walk)

For a total of 3 facts.

CLIPS>

自 定 义 事 实 声 明 , 必 需 指 定 一 个 事 实 名 , 如 上 面 的 walk, 跟 在 关 键 字

deffacts 的 后 面 ,事 实 名 后 面 可 以 跟 由 双 引 号 包 含 的 注 释 。同 规 则 中 的 注 释

一 样 , 当 CLIPS 载 入 (deffacts)事 实 时 , (deffacts)的 注 释 将 会 被 保 留 。 事 实

名 或 注 释 后 面 便 是 将 要 被 声 明 到 事 实 表 中 的 事 实 , 自 定 义 的 事 实 由 CLIPS

的 (reset)命 令 声 明 添 加 。

事 实 (initial-fact)由 (reset)命 令 自 动 添 加 进 来 ,并 且 它 的 事 实 标 识 符 一 直

是 f-0 。 即 使 没 有 任 何 自 定 义 的 声 明 , (reset) 命 令 也 会 自 动 声 明 事 实

(initial-fact)。在 CLIPS 的 早 期 版 本 中 ,该 事 实 被 用 来 激 活 一 些 类 型 的 规 则 ,
但是现在它早已不作此目的使用了。它被用来对那些显式匹配于该事实的

程序向后兼容。

(reset) 命 令 较 之 (clear) 命 令 的 一 个 好 处 是 , 它 不 会 丢 弃 所 有 的 规 则 。

(reset)命 令 使 规 则 完 整 无 缺 , 而 (clear)命 令 将 会 移 除 所 有 议 程 中 的 规 则 , 并

移 除 所 有 事 实 表 中 的 旧 的 事 实 。用 (reset)命 令 是 开 始 一 个 程 序 执 行 的 首 选 方

法,特别是之前程序已经在运行并且事实表已经被旧的事实打乱时。

总 而 言 之 , (reset)命 令 作 用 于 事 实 有 三 点 :

(1)将 存 在 的 事 实 从 事 实 表 中 移 除 , 同 时 也 会 移 除 议 程 中 的 激 活 规 则 。

(2)声 明 事 实 (initial-fact)

(3)声 明 已 自 定 义 (deffacts)声 明 的 事 实 。

事 实 上 ,(reset)命 令 对 于 对 象 也 有 相 似 的 作 用 。它 可 以 删 除 实 例 ,创 建

initial-object, 声 明 添 加 自 定 义 实 例 (definstances)。

选择性消除

undeffacts 命 令 的 作 用 是 通 过 消 除 内 存 中 的 自 定 义 事 实 来 撤 销 (deffacts)

声明事实。举个例子:

CLIPS>(undeffacts walk)

CLIPS>(reset)

CLIPS>(facts)

f-0 (initial-fact)

For a total of 1 fact.

CLIPS>

这 个 例 子 演 示 了 怎 样 将 自 定 义 的 事 实 walk 消 除 。如 果 执 行 了 (undeffacts)

后,想保存一个自定义事实声明,则必须重新定义。你甚至还可以使用

(undeffacts)清 除 initial-fact 事 实 。除 了 事 实 之 外 ,CLIPS 还 允 许 使 用 undefrule


命令消除选定的规则。

注意

你 可 以 对 议 程 监 视 规 则 (watch rules) 触 发 和 监 视 激 活 (watch

activations)。监 视 统 计 (watch statistics)给 出 已 经 触 发 规 则 数 ,执 行 时 间 ,每

秒规则数,事实的平均数,事实的最大数,激活的平均数和激活的最大数

等信息。这些统计信息对于调整专家系统、优化运行速度非常有用。另一

个命令叫:
“ watch compilations”,用 来 显 示 当 规 则 被 加 载 时 的 信 息 。watch all

命令监视所有的项目。

使 用 dribble 命 令 打 印 和 查 看 信 息 到 屏 幕 或 磁 盘 , 将 会 使 你 的 程 序 稍 微

变 慢 , 这 是 因 为 CLIPS 需 要 花 较 多 的 时 间 去 打 印 或 保 存 信 息 到 磁 盘 中 去 。

dribble-on 命 令 会 将 所 有 的 信 息 存 储 到 被 选 入 对 话 框 的 磁 盘 文 件 中 , 直 到

dribble-off 命 令 的 输 入 才 终 止 。这 在 提 供 任 何 事 情 发 生 时 的 参 数 记 录 是 非 常

方便的。这两个命令如下:

(dribble-on <filename>)

(dribble-off <filename>)

另 外 一 个 有 用 的 调 试 命 令 是 (run), 该 命 令 提 供 了 一 个 触 发 规 则 数 目 的

可 选 参 数 。 举 个 例 子 , (run 21)命 令 将 会 告 知 CLIPS 运 行 , 并 当 21 个 规 则

触 发 后 停 止 。 (run 1)命 令 允 许 你 每 次 只 能 执 行 一 步 程 序 。 (step)命 令 等 同 于

(run 1)。

像 其 它 的 编 程 语 言 一 样 , CLIPS 也 提 供 断 点 (breakpoints)支 持 , 断 点 作

为 CLIPS 的 一 个 简 单 指 示 符 , 停 止 顺 序 执 行 而 优 先 执 行 指 定 规 则 。 断 点 由

set-break 命 令 设 置 。remove-break 命 令 将 移 除 已 经 设 置 的 断 点 。show-breaks

命 令 显 示 所 有 设 置 断 点 的 规 则 。 带 参 数 (rulename)的 规 则 句 法 如 下 所 示 :
(set-break <fulename>)

(remove-break <rulename>)

(show-breaks)

合适的匹配

你可能会遭遇到这种情况:当你确定某条规则应该被激活却没有被激

活 。这 也 许 是 你 的 CLIPS 中 存 在 有 漏 洞 ,因 为 对 于 一 个 技 术 非 常 好 的 CLIPS

的 程 序 员 来 说 ,应 该 不 可 能 是 他 们 的 问 题( 注 意 :为 开 发 者 做 些 商 业 宣 传 )

( 这 里 是 反 语 , 幽 默 )。

多数情况下,出现错误的原因是你书写规则的方式不对。为了给调试

提 供 帮 助 ,CLIPS 有 一 个 被 称 为 matches 的 命 令 ,这 个 命 令 可 以 告 诉 你 那 些

规则中的模式与事实可以匹配,哪些模式不能匹配而使规则不被激活。出

现错误的一个普遍原因是,模式中的元素拼写错误导致与事实不匹配或增

加的事实有拼写错误。

(matches) 的 参 数 为 需 要 被 检 查 匹 配 规 则 的 规 则 名 。 让 我 们 来 看 看

(matches)起 着 怎 样 的 作 用 , 首 先 输 入 (clear)命 令 , 然 后 输 入 下 面 的 规 则 :

(defrule take-a-vacation

(work done) ; 条件因素 1

(money plenty) ; 条件因素 2

(reservations made) ; 条件因素 3

=>

(printout t “ Let’ s go!!!” crlf))

下 面 将 显 示 (matches)命 令 的 用 法 , 输 入 所 示 的 命 令 , 注 意 (watch facts)

命令被开启,当你手动声明事实的时候,这是一个不错的方法,它可以提

供给你一次检查事实拼写的机会。
CLIPS>(watch facts)

CLIPS>(assert (work done))

==>f-1 (work done)

<Fact-1>

CLIPS>(matches take-a-vacation)

Matches for Pattern 1

f-1

Matches for Pattern 2

None

Matches for Pattern 3

None

Partial matches for CEs 1 – 2 ; CE 即 条 件 元 素

None

Partial matches for CEs 1 – 3

None

Activations

None

CLIPS>

通 过 (matches)命 令 ,可 以 看 到 事 实 标 识 为 f-1 的 事 实 与 规 则 中 的 第 一 个

模 式 或 称 之 为 条 件 因 素 可 匹 配 。规 则 可 能 有 N 条 模 式 ,术 语 部 分 匹 配 (partial

matches)是 关 于 第 N 个 模 式 与 第 一 个 事 实 匹 配 的 所 有 设 置 ,也 就 是 说 ,部 分

匹配开始于规则的第一个模式,终止于任何一个模式,但不包含最后一个

模 式 。 当 一 个 部 分 匹 配 不 能 成 立 时 , CLIPS 将 不 会 继 续 检 查 后 面 的 匹 配 。

举个例子,一个规则有四个模式,有可能第一个和第二个模式或第三个模

式 都 可 能 匹 配 成 功 ,但 ,只 有 当 所 有 的 模 式 都 匹 配 ,这 条 规 则 才 能 被 激 活 。
其他特性

这 里 有 一 些 其 他 有 用 的 关 于 自 定 义 事 的 命 令 。 举 个 例 子 , list-deffacts

命 令 将 会 列 出 当 前 CLIPS 载 入 的 所 有 自 定 义 事 实 的 事 实 名 。 另 一 个 有 用 的

命 令 是 ppdeffacts, 它 将 所 有 存 储 的 自 定 义 事 实 信 息 打 印 出 来 。

函数 作用

assert-string 以字符串作为参数执行一个字符声明和作为一个无字符串

事实的声明

str-cat 通 过 字 符 串 (string concatenation)从 单 项 目 中 构 建 一 个 单

引号字符串

str-index 返 回 第 一 次 出 现 子 串 的 字 符 串 索 引 (string index)

sub-string 返回一个字符串的子字符串

str-compare 执 行 字 符 串 比 较 (string compare)

str-length 返 回 字 符 串 的 长 度 (string compare)

sym-cat 返回连结符号

如 果 你 想 不 用 圆 括 号 来 输 出 多 变 量 , 最 简 单 的 方 法 就 是 用 string implode

function, implode$。

• 第四章 变量

没改变更甚于改变。

迄今为止,你已经了解了一些规则的类型,简单的阐述了规则的模式

与事实匹配的一些内容。在本章中,你将会学到一些更有用的匹配和处理

事实的方法。

认识变量
同 其 他 编 程 语 言 一 样 , CLIPS 也 通 过 变 量 (variables)来 存 储 值 。 与 事 实

不同的是,事实是静态的且不会改变,而变量的内容是随着其分配的值的

改 变 而 动 态 (dynamic)变 化 的 。 相 比 之 下 , 一 旦 一 个 事 实 被 声 明 , 它 的 字 段

仅仅只能被撤销和重新声明一个该字段的事实而修改,甚至,这些事实的

撤 销 和 声 明 修 改 (将 在 本 章 后 面 的 deftemplate 中 详 细 描 述 )是 通 过 你 所 知 道

的修改事实索引执行的。

变 量 名 , 或 者 称 之 为 变 量 标 识 符 (variable identifier), 通 常 被 写 在 一 个

问号的后面,即变量名。通用格式如下:

?<variable-name>

全局变量将在后面详细讲到,与上面的句法比较有些许不同。

如同其他的编程语言一样,变量名应该有一种好的命名方式,具有明

确的含义。一些有效的变量名实例如下:

?x ?noun ?color

?sensor ?valve ?ducks-eaten

在一个变量能够被使用之前,它必须被分配一个值。下面是一个没有

分 配 值 的 例 子 , 尝 试 输 入 下 面 的 代 码 , 你 将 会 看 到 CLIPS 会 响 应 一 个 错 误

消息:

CLIPS>(unwatch all)

CLIPS>(clear)

CLIPS>(defrule test

=>

(printout t ?x crlf))
[PRCCPDE3] Undefined variable x referenced in RHS of defrule.

ERROR:

(defrule MAIN::test

=>

(printout t ?x crlf))

CLIPS>

当 CLIPS 不 能 找 到 ?x 变 量 的 约 束 值 (value bound)时 , 便 会 抛 出 一 个 错

误 的 提 示 。 术 语 bound 意 味 着 对 变 量 所 分 配 的 值 。 只 有 全 局 变 量 约 束 于 所

有的规则。其他所有的变量均约束于一条规则。在一条规则被触发前后,

如 果 非 全 局 变 量 没 有 被 约 束 , 那 么 当 你 尝 试 调 用 该 变 量 时 , CLIPS 就 会 给

出一个错误提示。

果断点

一 个 变 量 的 惯 用 方 式 是 :在 LHS 中 匹 配 一 个 值 ,随 后 在 RHS 中 对 该 变

量进行约束。举例如下:

(defrule make-quack

(duck-sound ?sound)

=>

(assert (sound-is ?sound)))

声 明 事 实 (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))

<Fact-1>

CLIPS>(run)

Brian shot duck ; 今晚有鸭子吃了!

CLIPS>(assert (duckshoot duck Brian))

<Fact-2>

CLIPS>(run)

duck shot Brian ; 今 晚 吃 Brian!

CLIPS>(assert (duckshoot duck)) ; 丢失第三个字段

<Fact-3>

CLIPS>(run)

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))

<Fact-1>

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 <Fact-1>

CLIPS>(facts)

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)

Date Dopey (Dinky Mallard)

Date Dopey (Mallard)

Date Dopey ()

CLIPS>

如 你 所 见 ,在 LHS 中 ,多 模 式 是 $?name,而 在 RHS 中 ,只 能 用?name。输 入 并

运 行 , 你 将 看 到 所 有 有 资 格 入 选 的 Dopey 们 的 名 字 。 多 字 段 通 配 符 照 顾 到

所有字段的个数,同样,注意多字段变量返回时包含在圆括号里面。

假 设 你 想 匹 配 所 有 的 只 要 是 名 字 中 包 含 Dopey 的 鸭 子 , 比 一 定 非 得 是

它 们 的 姓 氏 。 下 面 的 例 子 将 会 匹 配 所 有 包 含 Dopey 的 事 实 并 打 印 出 它 们 的

名字:
CLIPS>(defrule dating-ducks

(bachelor $?first Dopey $?last)

=>

(printout t “ Date” ?first “ Dopey” ?last crlf))

CLIPS>(reset)

CLIPS(run)

Date () Dopey (Dinky Mallard)

Date (Dinky) Dopey ()

Date () Dopey (Mallard)

Date () Dopey ()

CLIPS>

这 里 的 模 式 将 与 所 有 的 只 要 是 包 含 Dopey 的 事 实 匹 配 。

单或多字段通配符也可以联合使用,举个例子,模式:

(bachelor ? $? Dopey ?)

意味着姓氏和名字的最后一个字可以是任意的,但是最后一个字之前一定是

Dopey。同 时 ,这 个 模 式 也 要 求 与 之 匹 配 的 事 实 至 少 包 含 有 四 个 字 段 ,因 为

$?可 以 匹 配 零 个 或 多 个 字 段 , 其 他 的 必 须 都 匹 配 一 个 字 段 。

尽管多字段在许多情况下的模式匹配中必不可少,但是它们的滥用会

带来许多的副作用,因为它们增加了系统的内存消耗,使执行速度变慢。

 作 为 一 种 普 通 的 规 则 类 型 ,你 可 以 仅 在 你 不 知 道 字 段 长 度 的 情 况

下 使 用 $?, 不 要 将 $?简 单 的 用 来 方 便 输 入 。

• 模式中变量的使用有一个非常重要和有用的属性,表述如下:

 变 量 在 被 首 次 绑 定 时 ,仅 在 规 则 内 保 留 其 值 ,包 含 LHS 和 RHS,

除 非 在 RHS 中 被 改 变 了 。
举个例子,在下面的规则中:

(defrule bound

(number-1 ?num)

(number-2 ?num)

=>)

如果有下面事实:

f-1 (number-1 0)

f-2 (number-2 0)

f-3 (number-1 1)

f-4 (number-2 1)

那 么 ,这 条 规 则 这 能 被 f-1,f-2 的 事 实 对 和 f-3,f-4 的 事 实 对 激 活 。f-1 不 能 与

f-4 同 时 进 行 匹 配 ,这 是 因 为 在 模 式 中 ,?num 已 经 绑 定 为 一 个 值 ,那 么 事 实

中 的 代 替 字 段 只 能 是 相 同 的 值 。 同 理 , 当 第 一 个 模 式 中 的 ?num 绑 定 值 到 1

的 时 候 , 第 二 个 模 式 中 ?num 的 值 也 必 须 为 1。 注 意 这 里 的 规 则 将 会 被 激 活

两次。

作 为 一 个 实 际 的 例 子 ,输 入 下 面 的 规 则 。注 意 相 同 的 变 量 ?name 被 同 时

用 在 模 式 中 。 在 (reset)和 (run)程 序 之 前 , 输 入 (watch all)命 令 , 这 样 你 便 可

以清楚看到执行了什么。

CLIPS>(clear)

CLIPS>(defrule ideal-duck-bachelor

(bill big ?name)

(feet wide ?name)


=>

(printout t “ The ideal duck is ” ?name crlf))

CLIPS>(deffacts duck-assets

(bill big Dopey)

(bill big Dorky)

(bill litter Dicky)

(feet wide Dopey)

(feet narrow Dorky)

(feet narrow Dicky))

CLIPS>(watch facts)

CLIPS>(watch activations)

CLIPS>(reset)

<==f-0 (initial-fact)

==>f-0 (initial-fact)

==>f-1 (bill big Dopey)

==>f-2 (bill big Dorky)

==>f-3 (bill litter Dicky)

==>f-4 (feet wide Dopey)

==>Activation 0 ideal-duck-bachelor: f-1,f-4

==>f-5 (feet narrow Dorky)

==>f-6 (feet narrow Dicky)

CLIPS>(run)

The ideal duck is Dopey

CLIPS>

当 程 序 运 行 时 , 第 一 个 模 式 与 Dopey 和 Dorky 匹 配 因 为 他 们 都 非 常 富

有 (big bills), 变 量?name 被 分 别 绑 定 到 他 们 的 名 字 , 当 CLIPS 尝 试 匹 配 第


二 个 模 式 的 时 候 , 只 有 ?name 绑 定 到 Dopey 的 能 满 足 模 式 (feet wide)。

幸运的鸭子

在生活中有许多情况出现,以系统化的方式行事是非常明智的。如果

你的期望方式不能解决问题的时候不妨试试系统化(如去一次次的结婚来

找 到 最 完 美 的 配 偶 这 样 的 普 通 算 法 )。

一个被总结起来的办法是保存名单(注意:如果你非常想给人留下深

刻 的 印 象 ,那 么 给 他 们 一 份 你 的 清 单 )。在 我 们 的 例 子 中 ,我 们 将 保 存 一 份

单身鸭子的清单,将最想找到婚姻的放在最前面。一旦一个理想的单身汉

被鉴定符合,我们便将他作为一只幸运鸭升至表的前列。

下 面 的 程 序 显 示 了 通 过 增 加 两 个 规 则 到 ideal-duck-bachelor 规 则 ,执 行

该过程:

(defrule ideal-duck-bachelor

(bill big ?name)

(feet wide ?name)

=>

(printout t “ The ideal duck is ” ?name crlf)

(assert (move-to-front ?name)))

(defrule move-to-front

?move-to-front <- (move-to-front ?who)

?old-list <- (list $?front ?who $?rear)

=>

(retract ?move-to-front ?old-list)

(assert (list ?who ?front ?rear))

(assert (change-list yes)))


(defrule print-list

?change-list <- (change-list yes)

(list $?list)

=>

(retract ?change-list)

(printout t “ List is: ” ?list crlf))

(deffacts duck-bachelor-list

(list Dorky Dinky Dicky))

(deffacts duck-assets

(bill big Dicky)

(bill big Dorky)

(bill litter Dinky)

(feet wide Dicky)

(feet narrow Dorky)

(feet narrow Dinky))

最 初 的 表 在 duck-bachelor-list 自 定 义 事 实 中 给 出 , 当 程 序 运 行 后 , 将 会

提供一个新的最有可能候选的表。

CLIPS>(unwatch all)

CLIPS>(reset)

CLIPS>(run)

The ideal duck is Dicky

List is :(Dicky Dorky Dinky)


CLIPS>

注 意 move-to-front 规 则 中 的 声 明 (change-list yes) , 没 有 这 条 声 明 ,

print-list 规 则 将 会 总 是 触 发 在 初 始 事 实 。 该 声 明 是 一 个 关 于 用 控 制 事 实

(control fact)来 控 制 另 一 个 规 则 激 活 的 例 子 , 你 应 该 仔 细 的 学 习 这 个 例 子 并

弄 清 为 什 么 使 用 它 。 另 一 个 控 制 方 法 是 模 块 , 将 会 在 CLIPS 参 考 指 南 中 详

细讨论。

move-to-front 规 则 移 除 旧 的 名 单 , 增 加 新 的 名 单 。 如 果 旧 的 名 单 没 有

被 移 除 的 话 , 那 么 print-list 规 则 能 激 活 两 个 规 则 到 议 程 中 , 但 是 只 有 一 个

被 触 发 。 只 有 一 个 被 触 发 的 原 因 是 print-list 规 则 移 除 了 控 制 事 实 调 用 同 一

规则的其他激活。你不会提前预知哪一个将会被触发,所以在打印的时候

新的表单也许会被旧的替代了。

• 第五章 格式

Style today ,gone tomorrow

本 章 中 , 你 将 学 习 关 键 词 deftemplate 的 用 法 , deftemplate 代 表 定 义 模

板 (define template)的 意 思 。 这 个 关 键 词 能 帮 助 你 写 出 具 有 明 确 定 义 模 式 的

规则。

“精彩”先生

自 定 义 模 板 (deftemplate)类 似 于 C 语 言 中 的 结 构 定 义 。deftemplate 定 义

模 式 中 一 组 相 关 的 字 段 ,这 类 似 于 在 C 语 言 中 用 结 构 来 定 义 一 组 相 关 数 据 。

自 定 义 模 板 是 由 一 些 被 命 名 为 slot 的 字 段 构 成 的 表 。 自 定 义 模 板 允 许 通 过

字段名而不一定由指定的字段顺序来进行存取。在专家系统程序中,自定

义模板有助于编写好的格式,同时它在软件工程中也是非常有用的。

slot 被 称 之 为 单 槽 (single-slot)或 多 槽 (multislot)。 单 槽 包 含 且 仅 包 含 一


个字段,而多槽则可以包含零个或多个字段。自定义模板中可以使用任意

数 目 的 单 槽 和 多 槽 。 写 一 个 槽 的 时 候 , 先 写 字 段 名 (attribute)后 面 紧 跟 着 字

段值。注意,一个只有一个槽值的多槽并没有单槽那样的严格。类比想像

一下,将多槽看成是一个碗橱,里面也许有许多的碗碟。碗橱里只有一个

碟 子 却 并 不 等 同 于 就 一 个 单 独 的 碟 子 (单 槽 )。 然 而 , 单 槽 的 槽 值 ( 或 变 量 )

也可以与只有一个字段的多槽(或多槽变量)相匹配。

下面是一个有关自定义模板的例子,通过考察每个鸭子的属性来判定

哪个最有可能是母鸭的最佳婚姻对象。

Attributes Value

name “Dopey Wonderful”

assets rich

age 99

prospect 关 系 的 自 定 义 模 板 可 以 被 写 成 如 下 形 式 ,空 格 和 注 释 用 来 增 加

程序的可读性。

(deftemplate prospect ; 自定义模板关系名

“ vital information” ; 可选注释

(slot name ; 字段名

(type STRING) ; 字段类型

(default ?DERIVE)) ; 字段“名字”的默认值

(slot assets

(type SYMBOL)

(default rich))

(slot age

(type NUMBER) ; NUMBER 类 型 可 以 是 整 型 INTEGER 或 浮

点 型 FLOAT

(default 80)))
在本例中,自定义的构成如下:

 一个自定义模板关系名

 字段属性

 字 段 类 型 ,可 以 是 一 下 任 意 一 种 允 许 的 类 型 :SYMBOL,STRING,

NUMBER 或 其 他 。

 字段的默认值

该 部 分 自 定 义 模 板 包 含 有 三 个 单 槽 , 分 别 为 name, asset 和 age。

如 果 没 有 外 部 声 明 值 被 定 义 ,则 当 (reset)执 行 后 ,CLIPS 会 插 入 自 定 义

模 板 的 默 认 值 (deftemplate default values)。 举 个 例 子 , 在 (clear)命 令 后 输 入

prospect 自 定 义 模 板 结 构 , 声 明 如 下 :

CLIPS>(assert (prospect))

<Fact-1>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (prospect (name “ ” )(assets rich)(age 80))

For a total of 2 facts.

CLIPS>

如 你 所 见 , CLIPS 已 经 为 name 字 段 插 入 了 string 类 型 默 认 的 值 — — “ ”。 同 样

的 , CLIPS 也 插 入 另 两 个 字 段 的 默 认 值 。 不 同 的 类 型 有 不 同 的 默 认 符 号 ,

如 对 字 符 串 STRINGl 来 说 ,空 字 符 串 为“ ”,对 INTEGER 为 整 数 0,对 FLOAT

为 浮 点 值 0.0 等 等 。关 键 字 ?DERIVE 自 动 为 槽 值 的 类 型 选 择 合 适 的 默 认 值 ,

如 对 于 字 符 串 STRING 类 型 为 空 字 符 串 “ ”。

你可以显式声明字段的值,如下例所示:
CLIPS>(assert (prospect (age 99)(name “ Dopey” )))

<Fact-2>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (prospect (name “ ” )(assets rich)(age 80))

f-2 (prospect (name “ Dopey” )(assets rich)(age 99))

For a total of 3 facts.

CLIPS>

注意上面的字段输入顺序是无关紧要的,虽然它们都是命名字段。

在 自 定 义 模 板 中 ,最 重 要 的 是 要 认 识 到 NUMBER 不 是 像 字 符 ,字 符 串 ,

整 型 和 浮 点 型 那 些 原 始 的 字 段 类 型 。 NUMBER 是 即 可 整 型 又 可 浮 点 的 混 合

类 型 ,这 对 于 那 些 在 编 程 中 并 不 需 要 在 乎 哪 种 数 字 类 型 存 储 是 非 常 有 用 的 。

NUMBER 的 两 种 类 型 之 一 指 定 类 型 如 下 所 示 :

(slot age

(type INTEGER FLOAT)

(default 80)))

再见

通常,一个有 N 个槽的自定义模板的一般结构如下所示:

(deftemplate <name>

(slot-1)

(slot-2)

(slot-N))
在 一 个 自 定 义 模 板 中 ,属 性 值 一 般 被 指 定 精 确 的 值 ,而 不 是 简 单 的 如 80 或 rich。

举个例子,在这个自定义模板中,一个值类型被指定。

字 段 值 可 以 被 显 式 声 明 , 也 可 以 给 出 一 个 值 的 范 围 。 allowed-values 可

以 是 任 意 的 原 始 类 型 , 如 字 符 (SYMBOL) , 字 符 串 (STRING), 整 型

(INTEGER), 浮 点 型 (FLOAT)等 。 举 例 如 下 :

Deftemplate Enumerated Values Example

allowed-symbols rich filthy-rich loaded

allowed-strings “ Dopey”“ Dorky”“ Dicky”

allowed-numbers 1 2 3 4.5 -2.001 1.3e-4

allowed-integers -100 53

allowed-floats -2.3 1.0 300.00056

allowed-values “ Dopey” rich 99 1.e9

对于同一个自定义模板字段,同时指定其数字范围和允许值是行不通

的 。 举 个 例 子 , 如 果 你 指 定 (allowed-integer 1 4 8), 这 与 数 值 范 围 1 到 10

的 (range 1 10)是 矛 盾 的 。如 果 一 些 数 字 碰 巧 是 连 续 的 ,如 1,2,3,那 么 你

可 以 指 定 一 个 范 围 (range 1 3) 可 以 精 确 匹 配 。 然 而 , 这 个 范 围 对 于

allowed-integers 来 说 是 多 余 的 了 。 因 此 , 范 围 和 允 许 值 是 互 斥 的 , 当 你 指

定了一个范围时就不能指定允许值,反之亦然。通常,范围属性不能被用

来 与 allowed-values, allowed-numbers, allowed-integer 或 allowed-floats 连

接。

去掉可选信息,一个规则使用到自定义模板如下所示:

CLIPS>(clear)

CLIPS>
(deftemplate prospect ; 自定义模板名

(slot name ; 字段名

(default ?DERIVE)) ; 字 段 name 的 默 认 值

(slot assets

(default rich))

(slot age

(default 80)))

CLIPS>

(defrule matrimonial_candidate

(prospect (name ?name)(asset ?net_worth)(age ?months))

=>

(printout t “ Prospect: ” ?name crlf

?net_worth crlf)

?months “ months old” crlf))

CLIPS>(assert (prospect (name “ Dopey Wonderful” )(age 99)))

<Fact-1>

CLIPS>(run)

Prospect: Dopey Wonderful

rich

99 months old

CLIPS>

• 注 意 在 声 明 事 实 的 命 令 中 , 并 没 有 指 定 rich 的 值 , 但 是 , rich 的 默 认 值 还

是 被 用 在 Dopey 上 了 。

如 果 assets 字 段 被 指 定 值 为 poor, 那 么 , 指 定 值 poor 会 重 载 assets 的

默 认 值 rich。 如 下 是 一 个 关 于 Dopey 那 吝 啬 的 侄 子 的 例 子 :

CLIPS>(reset)
CLIPS>(assert (prospect (name “ Dopey Notwonderful” )

(assets poor)(age 95)))

<Fact-1>

CLIPS>(run)

Prospect: “ Dopey Notwonderful”

Poor

95 months old

CLIPS>

自定义模板的模式可以像任意普通模式一样使用。举个例子,下面的

规则用来消除不受欢迎的对象。

CLIPS>(undefrule matrimonial_candidate)

CLIPS>(defrule bye-bye

?bad-prospect <- (prospect (assets poor)(name ?name))

=>

(retract ?bad-prospect)

(printout t “ bye-bye” ?name crlf))

CLIPS>(reset)

CLIPS>(assert (prospect (name “ Dopey Wonderful” )(assets rich)))

<Fact-1>

CLIPS>(assert (prospect (name “ Dopey Notwonderful” )(assets poor)))

<Fact-2>

CLIPS>(run)

bye-bye Dopey Notwonderful

CLIPS>
多槽的使用

迄 今 为 止 我 们 还 只 是 在 模 式 中 应 用 过 单 字 段 , 上 面 的 name, assets 和

age 字 段 值 均 是 单 值 。在 许 多 类 型 的 规 则 中 ,你 也 许 会 要 用 到 多 字 段 。自 定

义模板允许在一个多槽中使用到多槽值。

作 为 一 个 多 槽 的 例 子 , 假 设 你 想 将 关 系 prospect 的 name 写 成 多 字 段 ,

这 将 在 处 理 prospects 的 name 部 分 匹 配 上 有 很 大 的 灵 活 性 。 下 面 是 采 用 了

多槽和改进多字段部分匹配的自定义模板的定义。注意其中的多槽模式

$?name,现 在 被 用 来 与 所 有 组 成 name 的 字 段 匹 配 。为 了 方 便 起 见 ,同 时 给

出 一 个 自 定 义 事 实 (deffacts)。

CLIPS>(clear)

CLIPS>(deftemplate prospect)

(multislot name

(type SYMBOL)

(default ?DERIVE))

(slot assets

(type SYMBOL)

(allowed-symbols poor rich wealthy loaded)

(default rich))

(slot age

(type INTEGER)

(range 80 ?VARIABLE) ; 越老越好

(default 80)))

CLIPS>(defrule happy_relationship

(prospect (name $?name)(assets ?net_worth)(age ?months))

=>
(printout t “ Prospect: ” ?name crlf

?net_worth crlf

?months” moths old” crlf))

CLIPS>(deffacts duck-bachelor

(prospect (name Dopey Wonderful)(assets rich)(age 99)))

CLIPS>(reset)

CLIPS>(run)

Prospect: (Dopey Wonderful)

rich

99 months old

CLIPS>

在 输 出 中 ,Dopey 的 名 字 被 包 含 在 圆 括 号 里 ,这 表 明 了 这 是 一 个 多 槽 值 。

如 果 你 将 多 槽 与 单 槽 做 比 较 , 你 会 发 现 双 引 号 不 见 了 。 在 多 槽 中 , name 槽

并 不 是 一 个 字 符 串 ,CLIPS 将 name 做 为 两 个 单 独 的 字 段 Dopey 和 Wonderful

来看。

修改字段槽值

自定义模板大大的方便了对模式中指定字段的访问,因为期望字段能

被 它 的 槽 名 所 标 定 。可 以 利 用 修 改 (modify)行 为 修 改 指 定 的 自 定 义 模 板 中 的

槽来撤销并增加一个新事实。

作 为 一 个 例 子 , 看 看 当 单 身 鸭 子 Dopey Wonderful 失 去 了 它 所 有 的 鱼 ,

从 Donald 鸭 那 里 为 它 的 母 鸭 买 些 香 蕉 什 么 的 , 下 面 的 规 则 能 执 行 什 么 。

CLIPS>(undefrule *)

CLIPS>

(defrule make-bad-buys
?prospect <- (prospect (name $?name)

(assets rich)

(age ?months))

=>

(printout t “ Prospect: ” ?name crlf

“ rich” crlf)

?months “ months old” crlf crlf)

(modify ?prospect (assets poor)))

CLIPS>

(defrule poor-prospect

?prospect <- (prospect (name $?name)

(assets poor)

(age ?months))

=>

(printout t “ Ex-prospect: ” ?name crlf

Poor crlf

?months “ months old” crlf crlf))

CLIPS>(deffacts duck-bachelor

(prospect (name Dopey Wonderful)(asset ric h)(age 99)))

CLIPS>(reset)

CLIPS>(run)

Prospect: (Dopey Wonderful)

rich

99 months old

Ex-prospect: (Dopey Wonderful)

poor
99 months old

CLIPS>

如 果 你 输 入 (facts)命 令 , 你 会 发 现 先 前 的 事 实 f-1 被 (modify)撤 销 了 ,

取 而 代 之 的 是 一 个 新 的 事 实 f-2。

CLIPS>(facts)

f-0 (initial-fact)

f-2 (prospect (name Dopey Wonderful)(assets poor)(age 99))

For a total of 2 facts.

CLIPS>

make-bad-buys 事 实 被 prospect 中 指 定 的 assets 槽 的 槽 值 rich 激 活 , 该

条 规 则 在 modify 作 用 下 将 assets 的 槽 值 改 变 为 poor。 注 意 assets 槽 可 以 通

过槽名访问到。如果没有自定义模板,那将必须列出所有的单变量的字段

或 使 用 到 通 配 符 ,那 样 的 话 ,效 率 将 会 降 低 。poor-prospect 规 则 的 目 的 是 简

化 打 印 出 poor prospect, 且 证 明 了 make-bad-investments 规 则 确 实 改 变 了

assets 槽 。

• 第六章 功能

功能性是格式的对立面。

本章中,你将会学到一些关于模式匹配非常有用的函数和一些非常有

用的多字段变量。同时,你也将学到怎样进行数字计算。

~约束
让我们重新考虑设计一个帮助机器人穿越大街的专家系统的问题,你

可能已写好的一个规则如下:

(defrule green-light

(light green)

=>

(printout t “ Walk” crlf))

另一个可能涉及红灯情形的规则为:

(defrule red-light

(light red)

=>

(printout t “ Don’ t walk” crlf))

第 三 个 规 则 可 能 涉 及 当 一 个 walk-sign 显 示 不 能 行 走 的 规 则 , 这 比 绿 灯

要优先。

(defrule walk-sign

(walk-sign-says dont-walk)

=>

(printout t “ Don’ t walk” crlf))

前 面 所 给 的 规 则 都 很 简 单 ,且 没 有 涵 括 到 所 有 的 情 形 ,比 如 交 通 灯 故 障 。

举 个 例 子 ,当 红 灯 或 黄 灯 和 行 走 信 号 显 示 为 walk 的 时 候 ,机 器 人 该 怎 么 做 ?

一 个 处 理 该 情 形 的 办 法 是 通 过 使 用 字 段 约 束 (field constraint)来 限 制 LHS

模式中的值。字段约束的作用就像是对模式的约束。
一 种 字 段 约 束 被 称 之 为 连 接 约 束 (connective constraint)。 连 接 约 束 有 三

种 类 型 , 第 一 种 被 称 之 为 ~ 约 束 (~ constraint), 带 有 一 个 波 浪 字 符 “ ~ ”。

波浪字符后面跟一值,表示不允许为该值。

以 一 个 简 单 的 例 子 来 介 绍 ~ 约 束 ,假 设 你 想 写 一 个 规 则 ,当 交 通 灯 不 为

绿 灯 的 时 候 便 打 印 出“ Don’t walk”。一 种 方 式 是 对 每 种 交 通 灯 情 况 写 不 同

的规则,包括所有可能的情况:黄灯,红灯,闪烁的黄灯,闪烁的红灯,

闪烁的绿灯,瞬间黄灯,闪烁黄灯和瞬间红灯等等。然而,一个很简单的

方法是通过使用~约束写如下规则:

(defrule walk

(light ~ green)

=>

(printout t “ Don’ t walk” crlf))

通过使用~约束,该条规则可以适用于多条要求制定不同交通灯情况。

|约束

第 二 种 连 接 约 束 是 竖 线 约 束 (bar constraint),“ |” 约 束 用 来 表 示 允 许 一

组可以匹配的变量。

举个例子,假设你想写一条规则,当是黄灯或黄灯闪烁的时候,打印

出 “ Be cautious”。 下 面 的 例 子 显 示 了 竖 线 约 束 所 起 的 作 用 。

CLIPS>(clear)

CLIPS>(defrule cautious

(light yellow|blinking-yellow)

=>

(printout t “ Be cautious” crlf))


CLIPS>

(assert (light yellow))

<Fact-1>

CLIPS>(assert (light blinking-yellow))

<Fact-2>

CLIPS>(agenda)

0 cautious: f-2

0 cautious: f-1

For a total of 2 activations.

CLIPS>

&约束

第 三 种 连 接 约 束 是 与 约 束 (& connective constraint), 带 有 一 个 &符 号 。

与约束使连接约束同时匹配,你将在下面的例子中看到。与约束通常与其

他的连接约束一同使用,除此之外并不常用。举个例子,假设你想写一个

规则,该规则会被黄灯或闪烁黄灯的事实触发,这非常简单,用前面所学

到的竖线约束就可以解决。但是这里假设你还需要分清灯的颜色。

解 决 的 方 法 是 对 颜 色 color 绑 定 一 个 变 量 ,通 过 使 用 &匹 配 打 印 出 变 量 ,

这 里 “ &” 是 非 常 有 用 的 , 如 下 所 示 :

(defrule cautious

(light ?color&yellow|blinking-yellow)

=>

(printout t “ Be cautious because light is ” ?color crlf))

?color 变 量 将 会 被 限 制 在 可 与 匹 配 的 字 段 yellow 或 blinking-yellow。

“ &”与“ ~ ”搭 配 使 用 也 非 常 有 用 。举 个 例 子 ,假 设 你 想 写 一 个 规 则 ,
当交通灯不是黄色和红色时被触发,如下所示:

(defrule not-yellow-red

(light ?color&~ red&~ yellow)

=>

(printout t “ Go, since light is ” ?color crlf))

• 初级数字运算

除 了 处 理 符 号 事 实 , CLIPS 还 可 以 执 行 数 字 计 算 。然 而 , 你 始 终 要 明 白

专 家 系 统 语 言 如 CLIPS 并 不 是 设 计 用 来 做 些 数 字 运 算 的 。 虽 然 CLIPS 的 数

学函数功能非常强大,它们也只是用来对应用程序中进行一些数字修改而

已 。 其 他 的 语 言 如 FORTRAN 有 更 好 的 数 字 运 算 能 力 , 因 为 它 很 少 或 没 有

符 号 推 理 。 在 一 些 应 用 程 序 中 , 你 将 发 现 CLIPS 的 计 算 能 力 非 常 有 用 。

CLIPS 提 供 最 基 本 的 算 术 和 数 学 函 数 ,+,-,*,/,div,max,min,abs,

float 和 integer。 了 解 更 多 的 详 细 情 况 , 可 以 参 考 CLIPS 参 考 指 南 。

CLIPS 数 学 表 达 式 的 表 示 是 从 LISP 的 风 格 而 来 的 。 一 个 通 常 情 况 下 的

数 学 表 达 式 写 成 2+3, 在 LISP 和 CLIPS 中 则 必 须 被 写 成 前 缀 形 式 (prefix

form):( + 2 3)。在 CLIPS 的 前 缀 形 式 中 ,运 算 符 号 在 参 数 之 前 ,数 学 表 达

式 被 用 圆 括 号 括 住 。通 常 的 数 学 表 达 式 被 称 为 插 入 形 式 (infix form),因 为 数

学 运 算 符 号 在 参 数 (arguments)之 间 。

数 学 运 算 符 号 能 被 用 在 LHS 和 RHS 中 。 举 个 例 子 , 下 面 演 示 的 是 一 个

加 法 算 术 运 算 的 例 子 , 在 规 则 的 RHS 中 , 通 过 加 法 运 算 题 声 明 一 个 事 实 ,

该 事 实 中 包 含 数 字 ?x 和 ?y 的 和 。 注 意 例 子 中 的 注 释 是 用 插 入 式 表 示 的 。

CLIPS>(clear)

CLIPS>(defrule addition

(numbers ?x ?y)
=>

(assert (answer-plus (+ ?x ?y)))) ; ?x+?y 加 法

CLIPS>(assert (number 2 3))

<Fact-1>

CLIPS>(run)

CLIPS>(facts)

f-0 (initial-fact)

f-1 (number 2 3)

f-2 (answer-plus 5)

Fpra a total of 3 facts.

CLIPS>

算 术 运 算 符 可 以 用 在 LHS 中 ,等 于 号 (equal sign)---“ =”用 来 告 诉 CLIPS

计算后面的数学表达式,而不是用它来进行字面上的模式匹配。下面的例

子 用 来 显 示 LHS 中 三 角 形 斜 边 的 计 算 , 并 以 一 些 直 角 边 数 字 对 进 行 模 式 匹

配 。 求 幂 (exponentiatin)符 号 “ **” 用 来 对 x 和 y 的 值 进 行 乘 方 运 算 。 乘 方

符号的第一个参数是底数,第二个参数为指数。

CLIPS>(clear)

CLIPS>(deffacts database

(stock A 2.0)

(stock B 5.0)

(stock C 7.0))

CLIPS>(defrule addition

(numbers ?x ?y)

(stock ?ID =(sqrt (+ (** ?x 2)(** ?y 2))))

=>
(printout t “ Stock ID=“ ?ID” crlf))

CLIPS>(reset)

CLIPS>(assert (number 3 4))

<Fact-4>

CLIPS>(run)

Stock ID=B ; Stock ID 匹 配 乘 方 计 算

CLIPS>

扩展的参数

在许多数学运算符上,数学表达式中的参数可以扩展到两个以上。相

同顺序的算术运算对多个参数被执行。下面的例子阐述了三个参数的使用

方 法 ,计 算 过 程 从 左 至 右 。在 输 入 这 些 之 前 ,你 得 使 用 (clear)命 令 清 楚 所 有

旧的事实和规则。

(defrule addition

(numbers ?x ?y ?z)

=>

(assert (answer-plus (+ ?x ?y ?z)))) ; ?x+?y+?z

输 入 上 面 的 程 序 并 声 明 一 个 事 实 (numbers 2 3 4), 运 行 后 , 你 将 看 到 如

下 的 事 实 。注 意 ,事 实 索 引 在 载 入 程 序 前 ,会 因 你 事 先 输 入 的 (clear)或 (reset)

命令的不同而有所不同。

CLIPS>(facts)

f-0 (initial-fact)

f-1 (numbers 2 3 4)

f-2 (answer-plus 9)
For a total of 3 facts.

CLIPS>

多 参 数 CLIPS 表 达 式 的 插 入 等 量 可 以 被 表 示 为 :

arg [function arg]

这里的中括号意味可能是多条。

除 了 基 本 的 数 学 运 算 外 , CLIPS 还 有 扩 展 的 数 学 函 数 (Extended Math

function)功 能 , 包 括 trig, hyperbolic 等 等 。 完 整 的 列 表 请 参 看 CLIPS 参 考

指 南 。这 些 被 称 之 为 扩 展 的 数 学 函 数 ,因 为 通 常 认 为 基 本 的 数 学 运 算 为“ +”,

“ -” 等 。

混合结果的形式

在 表 达 式 的 处 理 中 , CLIPS 试 图 保 持 相 同 模 式 的 参 数 。 举 个 例 子 :

CLIPS>(+ 2 2) ; 两个整型参数的运算结果还是整型

CLIPS>(+ 2.0 2.0) ; 两个浮点参数的运算结果还是浮点型

4.0

CLIPS>(+ 2 2.0) ; 混合型参数输出浮点型结果

4.0

注 意 最 后 一 个 情 形 是 混 合 的 参 数 , CLIPS 自 动 将 结 果 转 变 为 双 精 度 浮 点 型 。

你 可 以 显 式 的 转 变 结 果 的 类 型 ,通 过 使 用 float 和 integer 运 算 符 ,如 下
所示:

CLIPS>(float (+ 2 2)) ; 显式转换整型到浮点型

4.0

CLIPS>(integer (+ 2.0 2.0)) ; 显式转换浮点到整型

圆 括 号 用 来 指 定 表 达 式 的 运 算 顺 序 。 在 ?x+?y*?z 的 例 子 中 , 通 常 的 计

算 顺 序 是 先 计 算?y*?z,然 后 再 与 ?x 相 加 。然 而 ,在 CLIPS 中 ,如 果 你 想 按

照此顺序计算的话,那么必须显式的使用圆括号,如下:

(defrule mixed-cals

(numbers ?x ?y ?z)

=>

(assert (answer (+ ?x (* ?y ?z)))))

在 这 条 规 则 中 ,最 内 一 层 的 圆 括 号 里 的 运 算 最 先 执 行 ,所 以 先 执 行 ?y*?z,然 后

再 与 ?x 相 加 。

• 约束变量

由 模 式 匹 配 在 LHS 中 分 配 一 个 值 给 变 量 类 似 于 通 过 绑 定 函 数 (bind

function)在 RHS 中 绑 定 (binding)一 个 值 到 变 量 。 如 果 同 一 个 变 量 被 重 复 的

使 用 到 , 那 么 在 RHS 中 绑 定 其 变 量 值 将 非 常 方 便 。

以一个简单的数学计算为例,让我们首先将答案绑定到一个变量,并

随 后 打 印 约 束 变 量 (bound variable)。
CLIPS>(clear)

CLIPS>(defrule addition

(numbers ?x ?y)

=>

(assert (answer (+ ?x ?y)))

(bind ?answer (+ ?x ?y))

(printout t “ answer is ” ?answer crlf))

CLIPS>

(assert (numbers 2 2))

<Fact-1>

CLIPS>(run)

answer is 4

CLIPS>(facts)

f-0 (initial-fact)

f-1 (numbers 2 2)

f-2 (answer 4)

For a total of 3 facts.

CLIPS>

(bind)同 样 可 以 被 用 在 RHS 中 , 用 来 绑 定 单 或 多 字 段 值 到 一 个 变 量 。

(bind)被 用 来 绑 定 零 个 , 一 个 或 多 个 值 到 一 个 变 量 , 而 不 带 “ $” 运 算 符 。

调 用 LHS 中 的 变 量 ,你 可 以 在 一 个 字 段 中 ,使 用“ $”运 算 符 创 建 多 字 段 模

式 ,如“ $?x”。然 而 ,在 RHS 中 ,


“ $”运 算 符 是 不 需 要 的 ,因 为 (bind)的 参

数 显 式 的 告 知 了 CLIPS 它 绑 定 值 的 个 数 。事 实 上 ,
“ $”运 算 符 在 RHS 中 只

是一个无用的附属物。

下 面 的 例 子 给 出 的 是 在 RHS 中 绑 定 多 个 变 量 。多 字 段 值 函 数 (multifield

value function),create$被 用 来 创 建 一 个 多 字 段 值 。它 的 基 本 语 法 如 下 所 示 :
(create$ <arg1> <arg2>… <argN>)

这 里 ,任 意 个 数 的 参 数 都 可 以 被 作 为 创 建 多 字 段 值 附 属 在 一 起 。这 些 多 字 段 值 ,

或 单 字 段 值 , 可 以 被 约 束 到 RHS 行 为 中 的 一 个 变 量 , 如 下 所 示 :

CLIPS>(clear)

CLIPS>(defrule bind-values-demo

=>

(bind ?duck-bachelors (create$ Dopey Dorky Dinky))

(bind ?happy-bachelor-mv (create$ Dopey))

(bind ?none (create$))

(printout t

“ duck-bachelors” ?duck-bachelors crlf

“ duck-bachelors-no-()” (implode$ ?duck-bachelor) crlf

“ happy-bachelor-mv ” ?happy-bachelor-mv crlf

“ none” ?none crlf))

CLIPS>(reset)

CLIPS>(run)

duck-bachelors (Dopey Dorky Dinky)

duck-bachelors-no-() Dopey Dorky Dinky

happy-bachelor-mv (Dopey)

none ()

CLIPS>

自定义函数

像 其 他 语 言 一 样 , CLIPS 允 许 程 序 员 通 过 deffunction 来 定 义 自 己 的 函
数 。 众 所 周 知 , deffuntion 可 以 帮 助 你 节 省 重 复 输 入 相 同 的 行 为 (actions)。

自 定 义 函 数 (deffunction)在 提 高 程 序 的 可 读 性 上 也 是 非 常 有 用 的 , 你 可

以像调用其他函数一样调用自定义函数,自定义函数也可以被用来当作其

他 函 数 的 参 数 使 用 。 在 自 定 义 函 数 中 , (printout)可 以 在 任 何 位 置 使 用 , 甚

至 不 是 作 为 最 后 一 个 行 为 ,因 为 打 印 的 一 个 副 作 用 是 调 用 了 (printout)函 数 。

自定义函数的通用语法如下所示:

(deffunction <function-name> [optional comment]

(?arg1 ?arg2 … ?argM [$?argN]) ; 参 数 表 ,最 后 一 个 为 可 选 多 字 段 参

(<action1> ; actionK 之 前 的 行 为 不 会 返 回 值 ,仅 最 后 一 个 行 为 返 回

<action2>

<action(K-1)>

<actionK>))

其 中 ,?arg 为 虚 拟 参 数 (dummy arguments),代 表 参 数 的 名 字 ,如 果 在 一

条规则中参数名相同,变量不会发生冲突。虚拟变量在其他书本中,通常

被 称 之 为 参 量 (parameter)。

尽管每个行为都可以从函数中返回值,这些都被自定义函数返回给用

户。自定义函数仅仅返回最后一个行为,该行为可能是个函数,一个变量

或一个常量。

下 面 的 例 子 是 一 个 用 来 计 算 三 角 形 斜 边 的 自 定 义 函 数 ,然 后 被 用 在 规 则

中。即使规则中作为虚拟参数的变量名都是一样的,也不会有冲突,这是

因为它们都是虚拟的,并不指代任何参数。
CLIPS>(clear)

CLIPS>(deffunction hypotenuse ; 函数名

(?a ?b) ; 虚拟参数

(sqrt(+ (* ?a ?a)(* ?b ?b)))) ; 行 为

CLIPS>(defrule calculate-hypotenuse

(dimensions ?base ?height)

=>

(printout t “ Hypotenuse=” (hypotenuse ?base ?height) crlf))

CLIPS>(assert (dimensions 3 4))

<Fact-1>

CLIPS>(run)

Hypotenuse=5.0

CLIPS>

自定义函数也通常被用在多字段值中,如下例子所示:

CLIPS>(clear)

CLIPS>(deffunction count ($?arg)

(length $?arg))

CLIPS>(count 1 2 3 a duck “ quacks” )

CLIPS>

其他特性

其 他 一 些 有 用 的 函 数 如 下 所 示 。更 多 的 信 息 ,请 参 看 CLIPS 参 考 指 南 。
函数 含义

round 四舍五入

integer 取整

format 格式输出

list-deffunctions 函数列表

ppdeffuntion 打印出函数

undeffunction 删除函数

length 字段的长度,或字符串中字母的个数

nth$ 指 定 存 在 的 字 段 , 否 则 为 nil

member$ 如 果 变 量 存 在 ,则 返 回 字 段 的 成 员 ,否 则 为

FALSE

subsetp 如果一个多字段值是另一多字段值的一部分则返回

TRUE, 否 则 返 回 FALSE

delete$ 删除给出数字字段内的值

explode$ 将多字段值以每个字符串元素返回

subseq$ 返回字段的指定范围

replace$ 替代指定的值

• 第七章 程序的控制

当你年轻的时候,你被世界所控制,当你老的时候,你将控制世界。

到 目 前 为 止 , 你 已 经 学 历 了 CLIPS 的 基 本 句 法 。 现 在 , 你 将 学 习 怎 样

将所学的句法应用到实际有用的程序当中去。同时,你还将会学到一些关

于输入的新的句法,怎样比较变量和产生循环。

读入函数

除 了 模 式 匹 配 外 , 规 则 还 可 以 通 过 其 他 方 式 获 取 信 息 。 CLIPS 可 以 通
过 使 用 读 入 函 数 (read function)来 读 入 用 户 输 入 的 键 盘 信 息 。

下 面 是 使 用 (read)命 令 来 输 入 数 据 的 例 子 。注 意 (read)命 令 在 新 的 一 行 中

插 入 光 标 后 并 不 需 要 多 余 的 (crlf)。 (read)自 动 将 光 标 重 置 到 新 的 一 行 中 。

CLIPS>(clear)

CLIPS>(defrule read-input

=>

(printout t “ Name a primary color” crlf)

(assert (color (read))))

CLIPS>

(defrule check-input

?color <- (color ?color-read&red|yellow|blue)

=>

(retract ?color)

(printout t “ Correct” crlf))

CLIPS>(reset)

CLIPS>(agenda)

0 read-input:*

For a total of 1 activation.

CLIPS>(run)

Name a primary color

green

CLIPS> ; 没 有 打 印 出 “ correct”

上 面 的 规 则 中 ,在 RHS 中 使 用 键 盘 输 入 ,不 用 指 定 任 意 LHS 中 的 模 式

就 可 以 非 常 方 便 的 被 触 发 ,且 当 (reset)出 现 后 自 动 的 被 激 活 。当 输 入 (agenda)
命 令 后 , 读 入 规 则 的 激 活 将 被 显 示 , 打 印 一 个 *号 , 而 不 是 像 事 实 标 识 符 ,

如 f-1。 *号 的 使 用 用 来 表 示 该 模 式 不 用 指 定 事 实 , 均 可 满 足 。

(read)函 数 并 不 是 可 以 读 入 所 有 键 盘 输 入 的 通 用 函 数 ,它 仅 能 读 入 一 个

字段。所以,当你想读入下面的:

primary color is red

那 么 ,只 有 第 一 个 字 段“ primary”能 被 读 入 。如 果 你 想 读 入 所 有 的 字 段 ,那 么

必须使用双引号将其包含起来。当然,一旦使用了双引号,那么就会被当

作 一 个 单 字 符 串 。 然 后 可 以 通 过 str-explode 或 sub-string function 访 问 子 字

符 串 , 如 “ primary”,“ color”,“ is” 和 “ and”。

(read)的 第 二 个 限 制 是 不 能 输 入 圆 括 号 ,除 非 使 用 双 引 号 。就 像 不 能 声

明 一 个 包 含 圆 括 号 的 事 实 一 样 , 也 不 能 使 用 (read)直 接 读 入 圆 括 号 。

readline 函 数 被 用 来 读 入 多 值 ,直 到 输 入 回 车 键 为 止 。该 函 数 将 读 入 的

数 据 作 为 一 个 字 符 串 。为 了 声 明 (readline)数 据 ,(assert-string)函 数 用 来 声 明

一 个 非 字 符 串 事 实 , 就 像 用 (readline)输 入 。 (assert-string)例 子 如 下 :

CLIPS>(clear)

CLIPS>(assert-string “ (primary color is red)” )

<Fact-1>

CLIPS>(facts)

f-0 (initial-fact)

f-1 (primary color is red)

For a total of 2 facts.

CLIPS>

(assert-string)的 参 数 必 须 是 一 个 字 符 串 , 下 面 的 例 子 是 使 用 (readline)
来声明一个多字段事实。

CLIPS>(clear)

CLIPS>(defrule test-readline

=>

(printout t “ Enter input” crlf)

(bind ?string (readline))

(assert-string (str-cat “ (” ?string “ )” )))

CLIPS>(reset)

CLIPS>(run)

Enter input

Primary color is red

CLIPS>(facts)

f-0 (initial-fact)

f-1 (primary color is red)

For a total of 2 facts.

CLIPS>

因 为 (assert-string)声 明 要 求 字 符 串 被 圆 括 号 括 住 ,(str-cat)用 来 将 括 号 括 住 字 符

串 ?string。

(read)和 (readline)还 可 以 被 用 来 读 入 文 件 信 息 ,通 过 指 定 文 件 的 逻 辑 名

作 为 其 参 数 。 了 解 更 多 的 信 息 , 请 参 看 CLIPS 参 考 指 南 。

• 效率问题

CLIPS 是 基 于 规 则 的 语 言 ,使 用 了 高 效 的 模 式 匹 配 算 法 ,即 Rete 算 法 ,

该 算 法 是 由 卡 内 基 -梅 隆 大 学 的 Charles Forgy 和 他 的 OPS 团 队 设 计 出 来 的 。

Rete 在 拉 丁 语 中 是 网 (net)的 意 思 , 用 来 描 述 模 式 匹 配 过 程 中 的 软 件 结 构 。
很 难 给 出 一 个 精 确 的 规 则 能 够 总 是 促 进 在 Rete 算 法 下 程 序 运 行 的 效

率。然而,下面的几点可以作为有所帮助的一般指南:

1.将 最 明 确 的 模 式 放 在 规 则 首 位 。无 界 变 量 和 通 配 符 的 模 式 应 该 放 在 规

则模式的后面。一个控制事实应该放在模式的最前面。

2.较 少 匹 配 事 实 的 模 式 应 该 先 行 以 减 小 部 分 匹 配 。

3.经 常 被 撤 销 和 声 明 的 模 式 , 不 稳 定 模 式 (volatile patterns), 应 该 放 在

模式列表的最后。

如你所见,这个指南有潜在的对立。一个非明确的模式可能只有少数

的 匹 配 ( 参 见 1 和 2), 那 么 它 应 该 怎 么 归 类 ? 以 上 的 指 南 都 是 以 减 少 一 个

推理引擎循环内部分匹配的变化为目的的。这也许要求程序员花更多的心

思在监视部分匹配上。一个简单易行的办法是买一台更快的电脑,或更换

一 个 加 速 主 板 。这 在 电 脑 硬 件 的 价 格 一 路 下 滑 ,人 力 价 值 一 路 飙 升 的 今 天 ,

听 起 来 很 具 吸 引 力 。 因 为 CLIPS 具 有 可 移 植 性 , 那 么 在 一 台 机 器 上 能 运 行

的代码应该同样能在另一台机器上运行无恙。

其他特性

条 件 检 验 元 素 (test conditional element)通 过 比 较 LHS 中 的 数 字 ,变 量 和

字 符 串 提 供 了 一 个 非 常 有 用 的 方 法 。(test)被 作 为 一 个 模 式 用 在 LHS 中 。只

有 当 (test)与 其 他 模 式 一 起 满 足 时 , 规 则 才 能 被 触 发 。

CLIPS 提 供 了 许 多 预 定 义 函 数 , 如 下 表 所 示 :

Predefined Functions

逻辑 算术

not Boolean not / 除法

and Boolean and * 乘法

or Boolean or + 加法
比较

eq 等 于 (任 何 类 型 )。 比 较 类 型 和 大 小

neq 不 等 于 (任 何 类 型 )

= 等 于 (数 字 类 型 )。 比 较 大 小

<> 不 等 于 (数 字 类 型 )

>= 大于等于

> 大于

<= 小于等于

< 小于

以 上 所 有 的 比 较 函 数 除 了“ eq”和“ neq”,在 被 用 于 比 较 一 个 数 字 与 一

个 非 数 字 的 时 候 都 会 提 示 错 误 信 息 。如 果 类 型 不 能 预 知 ,那 么 就 可 以 用“ eq”

和 “ neq” 了 。 eq 函 数 检 查 比 较 参 数 的 大 小 和 类 型 , 而 “ =” 函 数 仅 检 查 比

较数值型参数的大小,而与数字是否为整型还是浮点型无关。

CLIPS 的 逻 辑 函 数 (logical functions)为 and,or 和 not。 他 们 可 以 被 当 作

布 尔 型 函 数 使 用 在 表 达 式 中 。 CLIPS 中 ,真 和 假 由 符 号 TRUE 和 FALSE 表

示 。 注 意 在 CLIPS 中 , 必 须 用 大 写 来 表 示 逻 辑 值 。

除 了 以 上 预 定 义 的 函 数 外 ,你 还 可 以 在 C,Ada 或 其 他 程 序 语 言 中 写 外

部 函 数 (external functions)或 用 户 自 定 义 函 数 (userdefine functions), 然 后 链

接 到 CLIPS 中 来 。 这 些 外 部 函 数 被 用 来 作 为 任 意 的 预 定 义 函 数 使 用 。

CLIPS 同 时 还 有 在 LHS 中 指 定 确 定 的 与 条 件 元 素 (and conditional

element),或 条 件 元 素 (or conditional element) 和 非 条 件 元 素 (not conditional

element)。 使 用 “ not” 条 件 元 素 指 定 LHS 中 缺 损 的 事 实 。

改 变 信 息 以 符 合 实 际 , 被 称 之 为 正 确 性 保 持 (truth maintenance)。 也 就

是说,我们试图保持我们的心情以包容真实的信息,为的是减少与真实世

界的冲突。

当 人 类 能 相 当 简 单 的 做 这 些 的 时 候 (熟 能 生 巧 ),对 电 脑 来 说 却 很 难 ,这
是 因 为 计 算 机 不 能 正 常 的 得 知 哪 个 模 式 实 体 逻 辑 依 赖 (logically dependent)

另 一 个 模 式 实 体 。 CLIPS 还 支 持 正 确 性 保 持 特 性 , 该 特 性 将 在 内 部 标 记 出

哪些模式实体是依赖与另一些模式实体的。如果那些模式实体被撤销了,

CLIPS 会 自 动 撤 销 这 种 逻 辑 依 存 。逻 辑 条 件 元 素 (logical conditional element)

使 用 关 键 字 logical 包 含 模 式 ,表 明 模 式 匹 配 实 体 提 供 RHS 中 声 明 以 逻 辑 支

持 (logical support)。

虽 然 逻 辑 支 持 为 声 明 服 务 ,但 是 它 不 能 重 新 声 明 撤 销 事 实 。按 道 理 讲 ,

如 果 由 于 错 误 的 信 息 而 使 你 丢 失 了 某 些 东 西 , 你 将 不 能 再 找 回 来 了 (就 像 是

在 你 的 股 票 经 纪 人 劝 说 下 损 失 钱 财 一 样 )。

CLIPS 有 两 个 函 数 用 来 协 助 逻 辑 支 持 。 一 是 相 关 函 数 (dependencies

function),该 函 数 列 出 了 所 有 的 部 分 匹 配 ,这 些 匹 配 来 自 模 式 实 体 接 收 到 逻

辑 支 持 , 或 根 本 没 有 支 持 。 第 二 个 逻 辑 函 数 是 从 属 函 数 (dependents), 该 函

数 列 出 了 所 有 的 模 式 实 体 ,这 些 模 式 实 体 从 另 一 模 式 实 体 中 接 收 逻 辑 支 持 。

连 接 约 束 为 “ &”,“ |” 和 “ ~ ”。 另 一 种 字 段 约 束 被 称 之 为 谓 词 约 束

(predicate constraint),它 通 常 被 用 在 对 复 杂 字 段 的 模 式 匹 配 当 中 。谓 词 约 束

的 目 的 是 依 据 布 尔 表 达 式 的 结 果 来 约 束 字 段 。 如 果 布 尔 返 回 值 为 FALSE,

那么约束不被满足且模式匹配失败。你将会发现谓词约束在数字模式中非

常有用。

谓 词 函 数 (predicate function) 用 来 返 回 FALSE 或 一 个 非 假 值

(non-FALSE)。 谓 词 函 数 后 面 跟 着 冒 号 “ :” 被 称 之 为 谓 词 约 束 。“ :” 的 优

先 级 高 于“ &”,
“ |”或“ ~ ”,如 写 在 一 起 的 模 式 (fact ( : > 2 1)))。与“ 与

约 束 &” 的 典 型 使 用 为 “ &:”。

谓词函数 核查参数

(evenp <arg>) 正好是数字

(float <arg>) 浮点型数字

(integerp <arg>) 整型
(lexemep <arg>) 字符或字符串

(numberp <arg>) 浮点型或整型

(oddp <arg>) 奇数

(pointerp <arg>) 外部地址

(sequencep <arg>) 多字段值

(stringp <arg>) 字符串

(symbolp <arg>) 字符

专家系统中非常方便的获取全局值,有多种情形。举例而言,重新定

义 通 用 常 量 如 π 是 不 必 要 的 。 CLIPS 提 供 自 定 义 全 局 常 量 (defglobal

construct), 这 样 这 些 值 就 会 被 所 有 规 则 广 泛 知 道 。

另 一 个 非 常 有 用 的 函 数 是 随 机 数 字 。 CLIPS 有 随 机 函 数 (random

function),用 来 返 回 随 机 整 型 值 。CLIPS 的 随 机 数 字 函 数 实 际 上 返 回 的 是 伪

随 机 (pseudorandom)数 字 ,这 意 味 着 实 际 上 并 不 是 真 正 的 随 机 ,而 是 由 一 个

数 学 公 式 产 生 出 来 的 。 伪 随 机 数 字 能 满 足 多 数 目 的 , CLIPS 的 随 机 函 数 使

用 的 是 ANSI C 的 rand 库 函 数 ,该 函 数 可 能 在 不 支 持 它 的 电 脑 系 统 上 无 效 。

关 于 更 多 的 信 息 , 可 参 看 CLIPS 参 考 指 南 。

除 了 控 制 事 实 以 控 制 程 序 的 执 行 外 , CLIPS 提 供 了 一 个 更 直 接 的 控 制

事实的方式,那就是显式的声明规则的权值。使用权值进行显式声明的一

个 问 题 是 当 你 从 一 开 始 用 CLIPS 时 就 在 连 续 程 序 中 过 度 使 用 权 值 , 过 度 的

使用将会达不到使用基于规则语言的目的,基于规则的语言提供的是通过

规则应用最好表达的自然手段。同样的道理,对于强导向应用,程序语言

是 最 好 的 , 对 于 描 述 对 象 来 说 , 对 象 导 向 语 言 是 最 好 的 。 CLIPS 有 一 个 关

键 词 为 声 明 权 值 (declare salience), 被 用 在 声 明 规 则 的 优 先 级 别 上 。

权 值 被 设 置 在 一 个 数 字 范 围 ,最 低 为 -10000,最 高 为 10000。如 果 一 个

规 则 没 有 被 程 序 员 显 式 的 声 明 权 值 ,CLIPS 自 动 设 置 其 值 为 0。权 值 0 介 于

最大与最小值之间。权值为 0 并不意味着规则没有权值,相反,它有一个
中间的优先级。

CLIPS 提 供 一 些 过 程 程 序 设 计 结 构 , 这 些 结 构 可 以 用 在 RHS 中 。 这 些

结 构 是 while 和 if then els, 这 些 同 样 可 以 在 其 他 语 言 如 Ada, C 和 Java 中

找到。

另 一 个 与 (while)有 关 的 有 用 函 数 是 break, 它 结 束 (while)循 环 的 当 前 执

行 。return 函 数 立 即 结 束 当 前 执 行 的 自 定 义 函 数 ,类 属 函 数 ,方 法 或 消 息 句

柄。

任 何 函 数 都 可 以 从 RHS 中 调 用 ,这 大 大 增 强 了 CLIPS 的 功 能 。另 一 些

CLIPS 函 数 在 返 回 数 字 , 字 符 或 字 符 串 中 很 有 效 。 这 些 函 数 可 以 用 在 它 们

的 返 回 值 或 他 们 的 副 作 用 (side-effects) 中 。 一 个 仅 使 用 副 作 用 的 例 子 是

(printout),由 它 返 回 的 值 都 是 没 有 意 义 的 。(printout)的 重 要 性 在 它 的 输 出 的

副作用上。通常情况下,如果要到达你期望的效果,函数都会有嵌套的参

数的。

文 件 的 读 写 访 问 之 前 ,必 须 使 用 open 函 数 来 打 开 它 。一 次 打 开 文 件 的

个数取决于你的操作系统和硬件情况。当你不再访问一个文件的时候,你

应 该 使 用 close 函 数 去 关 闭 它 。除 非 文 件 是 关 闭 的 ,不 能 保 证 写 入 它 的 文 件

将会被保存。

CLIPS 通 过 逻 辑 名 (logical name)来 识 别 一 个 文 件 。 逻 辑 名 是 一 个 全 局

名 , 可 以 被 CLIPS 在 所 有 的 规 则 中 访 问 。 尽 管 逻 辑 名 可 以 与 文 件 名 是 相 同

的,但你最好使用不同的。逻辑名的另一个好处是你将很方便的替代一个

不同的文件名,而不需要做主程序的修改。

从 文 件 中 读 取 数 据 的 函 数 是 前 面 介 绍 的 (read)和 (readline)。唯 一 你 需 要

做的是必须制定文件名,作为这两个函数的参数。

使 用 (read)读 取 多 个 字 段 ,必 须 使 用 循 环 (loop)。即 使 是 使 用 (readline),

在 读 取 多 行 字 段 的 时 候 也 是 必 需 的 。使 用 while-loop 循 环 可 以 通 过 一 个 规 则

而连续触发。循环不能用来读文件或操作系统的末端,会出现错误信息。

为 了 防 止 这 个 问 题 , 在 如 果 你 尝 试 读 入 文 件 的 末 端 时 , CLIPS 会 返 回 一 个
EOF 字 符 字 段 。

评 价 函 数 (evaluation)---eval,被 用 来 评 估 任 意 的 字 符 串 或 字 符 ,除 了 自

定 义 类 型 结 构 , 如 defrule, deffacts 等 , 像 在 顶 端 输 入 一 样 。 build 函 数 用

于 自 定 义 类 型 结 构 ,是 eval 函 数 的 补 充 。build 函 数 评 估 字 符 串 和 字 符 就 像

被 在 顶 端 输 入 一 样 ,如 果 参 数 符 合 自 定 义 类 型 结 构 如 deffrule,deffacts 等 ,

则 返 回 TRUE。

• 第八章 继承事项

获取财富最简单的办法莫过于继承遗产,其次就是剥削他人的劳动,

与财富联姻太像一项工作了。

本 章 为 CLIPS 面 向 对 象 编 程 的 概 述 章 节 。 与 基 于 规 则 的 编 程 不 同 , 基

于规则的编程中,你可以不用考虑系统中其它的东西,而及时投入进去,

编写规则,而面向对象的编程需要许多重要的背景资料。

如何客观

好 的 程 序 设 计 的 一 个 重 要 特 性 是 应 具 有 灵 活 性 (flexibility)。 不 幸 的 是 ,

结构化编程技术刻板的方法论不能提供快速,可靠和高效变化的灵活性。

面 向 对 象 编 程 范 式 (object-oriented programming (OOP) paradigm) 提 供 了 这

种灵活性。

术 语 — — 范 式 来 源 于 希 腊 语 paradeigma, 意 思 是 一 个 模 型 , 例 子 或 模

式。在计算机科学中,范式为一致的,有组织的尝试解决问题的方法论。

当 前 , 有 很 多 的 设 计 范 式 , 如 OOP , 过 程 式 的 (procedural), 基 于 规 则 的

(rule-based)和 联 系 式 的 (connectionist)。术 语 — —“ 人 工 神 经 网 络 ”(artificial

neural systems), 是 旧 术 语 “ 联 接 ” (connectionist)的 现 代 同 义 词 。

传 统 的 程 序 设 计 是 过 程 化 的 ,因 为 它 强 调 在 解 决 问 题 中 的 算 法 和 程 序 。

有 相 当 多 的 语 言 被 开 发 以 支 持 这 样 的 过 程 化 范 式 , 如 Pascal, C, Ada ,
FORTRAN 和 BASIC 。 这 些 语 言 同 样 也 适 用 于 面 向 对 象 的 设 计

(object-oriented design (OOD)),它 们 通 过 增 加 延 伸 或 利 用 程 序 员 的 设 计 方 法

学 。相 比 而 言 ,新 的 语 言 已 经 被 设 计 出 来 ,用 以 提 供 OOP,与 OOD 并 不 一

样 。 你 可 以 在 任 意 语 言 中 使 用 OOD, 甚 至 汇 编 语 言 。

CLIPS 提 供 三 个 范 式 : 规 则 , 对 象 和 过 程 。 你 将 在 CLIPS 面 向 对 象 语

言 (CLIPS Object-Oriented Language (COOL)) 中 了 解 到 更 多 关 于 对 象 的 信

息 ,COOL 整 合 了 规 则 和 过 程 两 个 CLIPS 基 本 范 式 。CLIPS 通 过 类 属 函 数 ,

自 定 义 函 数 和 用 户 自 定 义 外 部 函 数 来 支 持 过 程 化 范 式 。视 应 用 程 序 的 不 同 ,

你可以选择使用规则,对象,过程或是它们的组合。

与其利用单个的范式,我们的哲学是:多种专门工具,多范式途径要

比尝试去强制所有人使用单一万能的工具。类似的,你可以用锤子和钉子

来固定一切,但同时也有很多其他首选的方法来固定某物。举个例子,想

象 一 下 你 用 锤 子 和 钉 子 来 代 替 拉 链 来 扣 紧 你 的 裤 子 吧 。( 注 意 :如 果 有 人 使

用 锤 子 和 钉 子 来 扣 紧 裤 子 , 那 么 请 联 系 吉 尼 斯 世 界 纪 录 吧 !)

OOP 中 ,类 是 描 述 具 有 相 同 特 性 和 属 性 (attributes)的 对 象 的 模 板 。注 意

这 里 使 用 的 术 语 — —“ 模 板 ”与 前 面 所 讲 的 自 定 义 模 板 (deftemolate)是 两 码

事。这里,模板一词用的是工具的表意,用来构建具有相同属性的对象。

类似的,直尺是画直线的模板,饼干模子是做出曲奇的模板。

对象的类在层次和图线上被安排来描述系统中对象之间的关系。每个

类 都 是 实 际 系 统 或 其 他 一 些 我 们 正 尝 试 塑 造 的 逻 辑 系 统 的 抽 象 。举 个 例 子 ,

一个现实系统的抽象有可能是一辆汽车,逻辑系统的另一个抽象模型可能

是 金 融 证 券 ,如 股 票 ,契 约 或 复 数 。术 语 — —“ 抽 象 ”一 词 引 用 于 两 点 ,
( 1)

对 现 实 对 象 或 其 他 我 们 正 尝 试 塑 造 系 统 的 抽 象 描 述 。( 2) 用 术 语 类 表 示 一

个 系 统 的 过 程 。抽 象 是 一 个 真 正 OOP 语 言 所 广 泛 接 受 的 五 个 特 征 之 一 。其

他 分 别 是 继 承 (inheritance), 封 装 (encapsulation), 多 样 性 (polymorphism)和


动 态 绑 定 (dynamic binding) 。 当 你 通 读 本 书 后 将 会 对 以 上 术 语 有 所 详 细 了

解 。 CLIPS 支 持 以 上 五 种 特 性 。

抽象一词意味着我们不用关注一些非实质性的细节。复杂系统的抽象

描 述 是 一 种 集 中 于 指 定 目 标 重 大 信 息 的 精 简 描 述 。那 样 ,系 统 就 被 更 简 单 ,

易懂的模型表示了。作为一个熟悉的例子,当某人开车的时候,他利用的

是包含两个部分——转向轮和油门的抽象驾驶模型。这样,人们不会去 关

心组成摩托车的上百个零部件和内部燃烧发动机的理论知识,交通法规等

等了。知道怎样使用转向轮和油门就是他们驾驶的抽象模型。

继 承 是 OOP 的 五 个 基 础 特 性 之 一 。类 在 继 承 的 设 计 上 ,遵 循 将 最 一 般

的类放在顶层,最特殊化的类放在底层。这允许新的类被作为特殊定制或

对已存在类修改而重新定义。

继承的使用可以大大加速软件的开发和增加可靠性,因为建立一个新

的 软 件 , 不 必 要 每 次 从 编 写 新 的 程 序 白 手 起 家 。 OOP 利 用 可 再 用 代 码

(reusable code)使 得 以 上 变 得 简 单 。OOP 程 序 员 常 常 利 用 包 含 成 百 上 千 个 对

象的对象库,这些对象能被使用或作为设计新程序而修改。除了公共领域

的对象库,还有许多公司市场商业化对象库。尽管可再用软件组件的概念

早 已 在 1960 的 FORTRAN 子 程 序 库 中 被 贯 彻 ,但 是 它 还 从 未 被 如 此 成 功 的

应用于通用软件开发中。

定 义 一 个 类 , 你 必 须 指 定 被 定 义 类 的 一 个 或 多 个 父 类 (parent classes)或

超 类 (superclasses)。关 于 超 类 的 比 如 ,每 个 人 都 有 父 母 ,没 有 哪 个 人 是 天 然

就 存 在 的( 尽 管 有 些 时 候 你 也 许 想 知 道 某 些 人 是 否 真 的 有 父 母 )。超 类 相 对

的 是 子 类 (child class)和 亚 类 (subclass)。

这 些 决 定 新 类 的 继 承 。 子 类 继 承 属 性 来 源 于 一 个 或 多 个 超 类 。 COOL

中 的 属 性 一 词 引 用 于 对 象 的 道 具 (properties), 被 命 名 为 槽 (slot)来 描 述 它 。

举个例子,一个表示人的对象可能包含有姓名,年龄,地址等等的槽。

实 例 (instance)是 拥 有 了 槽 值 的 对 象 ,如 约 翰 .史 密 斯 ,28 岁 ,清 湖 市 主

街 道 1000 号 。 底 层 类 自 动 继 承 高 层 类 的 槽 , 除 非 某 个 槽 被 显 式 的 关 闭 了 。
除了继承的槽值被设置所有的属性,新的槽也被定义来描述类。

对 象 的 行 为 (behavior) 由 它 的 消 息 句 柄 (message-handlers) 或 槽 的 操 作

(handlers)。对 象 的 消 息 句 柄 对 消 息 (messages)响 应 和 执 行 要 求 的 行 为 。举 个

例子,发送消息:

(send [John_Smith] print)

将 引 起 相 应 的 消 息 句 柄 以 打 印 实 例 John_Smith 的 槽 值 。实 例 通 常 被 指 定 在 中 括

号 [](brackets)内 。 消 息 由 send 函 数 开 始 , 后 面 跟 实 例 名 , 消 息 名 和 所 有 要

求 的 参 数 。 举 个 例 子 , 在 打 印 消 息 的 情 况 下 , 没 有 参 数 。 CLIPS 的 对 象 就

是一个类的实例。

对 象 内 槽 和 操 作 的 封 装 是 OOP 广 泛 接 受 的 五 个 特 性 之 一 。封 装 一 词 的

意思是类按照它的槽和操作被定义。尽管一个类的对象可以继承它超类的

槽 和 操 作 ,( 一 些 例 外 稍 后 再 讲 ), 如 果 没 有 发 送 消 息 到 对 象 , 对 象 的 槽 值

不会被改变或消除。

CLIPS 的 根 类 (root class)或 简 单 根 (root)是 被 称 之 为 OBJECT 的 预 定 义

系 统 类 (predefined system class)。 预 定 义 系 统 类 USER 是 OBJECT 的 子 类 。

• 继承

一 个 例 子 ,假 设 我 们 想 定 义 一 个 类 ,名 为 UPPIE( 优 皮 ),是 城 市 专 业

人 员 (urban professional)的 口 语 化 称 呼 。 注 意 在 本 书 中 , 我 们 都 采 用 以 大 写

来书写类的惯例。

图 1.1 说 明 了 UPPIE 们 怎 样 从 根 类 OBJECT 中 得 到 继 承 的 。注 意 ,UPPIE

被 作 为 USER 的 子 类 来 定 义 。 盒 子 或 节 点 用 来 表 示 类 , 连 接 箭 头 被 称 为 链

接 (links)。 直 线 常 被 用 来 代 替 箭 头 以 便 简 化 画 图 。 同 理 , 因 CLIPS 仅 支 持

is-a 链 接 , 从 现 在 起 ,“ is-a” 关 系 将 不 被 靠 近 每 个 链 接 显 式 书 写 。
我 们 将 遵 循 类 之 间 关 系 (relationship)的 惯 例 为 :箭 头 的 末 端 为 子 类 ,箭

头 所 指 的 为 超 类 。图 1.1 中 的 关 系 遵 循 该 惯 例 。另 一 个 可 能 的 惯 例 是 用 箭 头

指向子类。

is-link 链 接 指 示 从 一 个 类 到 它 的 子 类 槽 的 继 承 。一 个 类 可 能 有 零 个 或 多

个 子 类 。除 了 OBJECT,所 有 的 类 必 须 有 个 超 类 。UPPIE 继 承 了 USER 所 有

的 槽 , USER 又 继 承 了 OBJECT 所 有 的 槽 , 也 就 是 UPPIE 继 承 了 OBJECT

所 有 的 槽 。 继 承 的 理 论 同 样 适 用 于 每 个 类 的 消 息 处 理 。 举 个 例 子 , UPPIE

继 承 了 USER 和 OBJECT 所 有 的 操 作 。

槽 与 操 作 的 继 承 在 OOP 中 尤 其 重 要 ,它 意 味 着 你 不 用 对 每 个 已 经 定 义 过 的 类 的

对象属性和行为重新定义。相反,每个新的类继承了它的高层类所有的属

性和行为。因为新的行为是继承的,它可能本质上减少了操作的验证与测

试 (verification and validation (V&V))。 V&V 实 质 上 意 味 着 合 理 的 打 造 产 品

并满足所有的要求。验证和测试软件的任务可能比软件开发本身要花更多

的时间和金钱,特别是如果该软件能影响到人类生活和财产。操作的继承

允许高效再利用

已存在的代码和加快开发速度。

CLIPS 中 使 用 自 定 义 类 (defclass)结 构 来 定 义 一 个 类 。 类 UPPIE 被 定 义

声明如下:

(defclass UPPIE (is-a USER))

注 意 与 图 1.1 中 UPPIE-USER 关 系 中 的 相 似 点 和 (defclass)结 构 。

你 不 必 输 入 USER 或 OBJECT 类 , 因 为 它 们 都 是 预 定 义 的 类 , CLIPS

已 经 知 道 它 们 之 间 的 关 系 。事 实 上 ,如 果 你 尝 试 定 义 USER 和 OBJECT,将

会 出 现 错 误 消 息 , 这 是 因 为 你 不 能 改 变 预 定 义 的 类 , 除 非 你 改 变 CLIPS 的
源代码。

因 为 CLIPS 是 区 分 大 小 写 (case-sensitive)的 ,命 令 和 函 数 必 须 以 小 写 输

入 。 预 定 义 系 统 类 , 如 USER 和 OBJECT 则 必 须 以 大 写 输 入 。 尽 管 你 可 以

以小写或大写输入用户定义类,我们将遵循以大写来输入类,以增加可读

性。

定义类(非槽)的自定义类命令基本的格式,如下:

(defclass <class> (is-a <direct-superclasses>))

类 表 (direct-superclasses) 被 称 为 直 接 超 类 优 先 表 (direct superclass

precedence list),因 为 它 定 义 了 类 直 接 链 接 的 超 类 。类 的 直 接 超 类 为 一 个 或

多 个 类 ,命 名 在 is-a 关 键 字 后 面 。在 我 们 的 例 子 中 ,类 DUCKLING 为 DUCK

的直接超类。注意,直接超类优先表中至少有一个直接超类。

如果直接超类表如下所示:

(defclass DUCK (is-a DUCKLING USER OBJECT))

那 么 , USER 和 OBJECT 同 样 也 是 DUCK 的 直 接 超 类 。 在 这 个 例 子 中 , 除 了

DUCKLING, 无 论 USER 与 OBJECT 哪 一 个 被 指 定 都 没 有 分 别 。 事 实 上 ,

因 为 USER 和 OBJECT 是 预 定 义 类 , 它 们 都 被 链 接 , 以 至 于 USER is-a

OBJECT, OBJECT 为 根 类 , 除 了 当 定 义 一 个 USER 的 子 类 时 ,你 不 必 分 清

它 们 。因 为 USER 仅 继 承 于 OBJECT,如 果 USER 已 经 被 指 定 ,那 么 OBJECT

就不必再被指定了。

类 的 非 直 接 超 类 (indirect superclasses)是 所 有 的 没 有 命 名 在 is-a 后 面 ,

继 承 属 性 槽 和 消 息 处 理 的 类 。 在 我 们 的 例 子 中 , 非 直 接 超 类 为 USER 和

OBJECT。一 个 类 从 所 有 它 的 直 接 和 非 直 接 超 类 中 继 承 槽 和 消 息 处 理 。因 此 ,

DUCK 继 承 于 DUCKLING, USER 和 OBJECT。


一 个 直 接 子 类 (direct subclass)由 一 个 单 链 接 与 它 上 面 的 类 连 接 。 一 个 非 直 接 子

类 (indirect subclass)有 多 于 一 个 的 链 接 。 图 1.2 概 述 了 类 术 语 。

根 类 OBJECT 是 唯 一 没 有 超 类 的 类 。

使用这些新奇属于允许我们声明:

Principle of OOP Inheritance

A class may inherit from all its superclasses.

这 是 一 个 简 单 ,但 在 OOP 中 被 全 面 利 用 的 很 有 用 的 概 念 。这 个 理 论 意

味着槽和消息处理可以被继承,以节省为新子类重新定义它们的麻烦。此

外,新子类的槽可以简单的用户化,以修 改和作为子类槽的合成。由允许

的简单灵活、已经存在的可再用代码,程序开发的时间和花销大大被缩减

了。另外,有效、存在的可再用代码,可以减少验证和确认的任务量。一

旦这些代码被释放,上述所有的优点可以推进程序维持调试,修改和增强

等任务。

理论上使用“可能”的原因是强调从类中继承的槽,可能被包含一个

非继承面的类槽定义封锁。

类 的 直 接 和 非 直 接 类 都 是 那 些 依 赖 于 OBJECT 的 继 承 路 径 (inheritance

path)。继 承 路 径 是 一 组 类 与 OBJECT 间 的 关 联 节 点 。在 我 们 的 例 子 中 ,DUCK

的 单 继 承 路 径 为 DUCK,DUCKLING,USER 和 OBJECT。后 面 的 例 子 ,如 图

1.6 中 , 你 将 发 现 一 个 类 到 OBJECT 有 多 继 承 路 径 (multiple inheritance)。

图 1.3 阐 明 的 是 一 个 简 单 的 有 机 体 分 类 学 (taxonomy),图 解 了 自 然 界 中

的继承。术语分类学的意思为分类。生物分类学的目的是显示有机体之间

的 血 缘 关 系 。也 就 是 说 ,有 机 体 分 类 学 强 调 的 是 个 体 与 聚 群 之 间 的 相 似 性 。
在 如 图 1.3 的 分 类 中 ,所 有 的 连 接 线 都 是 is-a 链 接 。举 个 例 子 ,DUCK

is-a BIRD,BIRD is-a ANIMAL,ANIMAL is-a ORGANISM 等 等 。 尽 管 每 个 个

体 的 起 源 继 承 都 不 尽 相 同 , 但 是 MAN 和 DUCK 物 种 的 特 性 是 相 同 的 。

图 1.3 中 ,最 通 用 的 类 为 ORGANISM 处 在 最 顶 端 ,其 他 物 种 类 均 在 分

类 的 下 面 。 在 CLIPS 术 语 中 , 我 们 可 以 说 每 个 子 类 继 承 了 父 类 的 槽 。 举 个

例 子 , 哺 乳 动 物 是 胎 生 的 暖 血 动 物 , 除 鸭 嘴 兽 外 , MAN 类 继 承 了 父 类

MAMMAL 类 的 属 性 。 MAMMAL 的 直 接 超 类 为 ANIMAL, 且 直 接 子 类 为

MAN, MAMMAL 的 非 直 接 超 类 为 ORGANISM。

其 他 的 类 ,如 BIRD,DUCK 等 都 与 MAMMAL 无 关 ,因 为 它 们 不 是 在

从 原 始 类 ORGANISM 下 来 的 同 一 条 继 承 路 径 (inheritance path)中 。 继 承 路

径 为 任 意 一 条 从 一 个 类 到 另 一 个 类 , 不 包 含 回 溯 和 重 新 的 路 径 。 类 PLANT

不 在 MAMMAL 的 继 承 路 径 当 中 , 因 为 在 达 到 MAMMAL 之 前 , 我 们 必 须

回 溯 到 ORAGNISM。这 样 ,MAMMAL 就 不 能 自 动 获 取 PLANT 和 其 他 不 在

其继承路径上类的任何槽。继承模型反映了真实的世界,否则我们头上的

头发也许会被杂草代替了。

• 非法继承类

现 在 你 已 经 对 类 有 了 初 步 的 认 识 , 让 我 们 在 图 表 1.1 的 UPPIE 中 增 加

一些其他的类,以使的例子更具有实际意义。通过增加低层类的开发类型

正 是 OOP 的 方 式 , 由 最 一 般 到 最 特 殊 的 方 式 增 加 类 。

图 1.5 所 示 的 是 非 合 法 YUKKIE 的 继 承 图 表 。 为 了 简 便 , 这 里 没 有 标

出 OBJECT 和 USER 类 。 图 1.5 是 一 个 树 (tree)形 分 级 系 统 , 每 个 节 点 有 唯

一的父节点。
一个常见的树形组织结构例子是经常用在公司中,由总裁,副总裁,

部 门 领 导 ,经 理 等 组 成 的 雇 员 等 级 分 层 (hierarchy)。在 这 种 情 况 下 的 等 级 分

层反映的是组织中每个人权利的大小。树通常被用来表示人的组织关系,

因 为 每 个 人 都 有 唯 一 的 老 板 , 当 然 CEO 除 外 。组 织 图 表 中 的 节 点 代 表 着 人

在组织中的职位,如总裁,副总裁等。连接各职位的线指代各职能部门分

支。在树中的链接通常被称之为分支。

图 1.5 中 , 除 了 YUKKIE 意 外 的 类 都 是 合 法 或 不 合 法 的 。 举 个 例 子 ,

SUPPIE is-a UPPIE, MUPPIE is-a UPPIE,YUPPIE is-a UPPIE, PUPPIE is-a

YUPPIE(没 有 中 年 YUPPIE 妈 妈 允 许 的 )。 我 们 也 同 样 可 以 说 YUKKIE is-a

YUPPIE 和 通 过 继 承 ,YUKKIE is-a UPPIE。 然 而 , 我 们 不 想 因 YUKKIE 与

PIPPIE 之 间 的 is-a 链 接 , 说 YUKKIE is-a PUPPIE。

YUKKIE 与 PUPPIE 之 间 的 is-a 链 接 是 一 个 自 然 错 误 , 让 人 误 以 为

YUKKIE 是 PIPPIE 的 子 辈( 事 实 上 ,当 她 生 孩 子 后 是 前 PUPPIE)。尽 管 在

YUKKIE 与 PUPPIE 之 间 的 is-a 链 接 允 许 YUKKIE 可 以 继 承 YUPPIE 和

UPPIE, 但 同 时 也 产 生 了 一 个 说 YUKKIE is-a PUPPIE 的 不 合 法 的 关 系 。 这

意 味 着 YUKKIE 将 继 承 PUPPIE 所 有 的 槽 。假 设 PUPPIE 的 一 个 槽 用 来 指 定

PUPPIE 怀 孕 的 月 份 数 ,这 意 味 着 每 个 Yuppie 小 孩 将 拥 有 相 同 的 槽 来 表 示 他

或她怀孕的月份数了。

更 正 图 表 是 有 可 能 的 。然 而 ,我 们 需 要 使 用 图 表 来 代 替 树 ,与 树 对 比 ,

树中除根节点,每个节点都有唯一的父节点,图表中的每个节点可以有零

个 或 多 个 节 点 与 之 连 接 。一 个 类 似 的 例 子 是 地 图 ,每 个 城 市 都 是 一 个 节 点 ,

道路将他们互相链接。树与图表的另一个不同是,多数类型的树都有分层

结构,而普通的图表没有。

图 1.6 显 示 的 是 非 法 Yuppie 类 YUKKIE。 一 个 新 类 CHILD 被 创 建 ,

YUKKIE 与 他 的 两 个 超 类 YUPPIE 和 CHILD 之 间 用 is-a 链 接 。注 意 ,YUKKIE

与 PUPPIE 之 间 的 非 法 链 接 不 再 存 在 了 。
这 里 采 用 图 表 , 因 为 YUKKIE 类 有 两 个 直 接 的 超 类 , 所 以 代 替 了 只 能

有 一 个 的 树 。 图 表 中 同 样 有 分 层 结 构 , 因 为 所 有 的 类 通 过 is-a 链 接 自 最 一

般 的 USER 到 最 特 殊 的 SUPPIE,MUPPIE,PUPPIE 和 YUKKIE 安 排 。通 过 图

1.6,我 们 可 以 说 YUKKIE is-a YUPPIE,同 时 也 可 以 说 YUKKIE is-a CHILD。

下 面 显 示 的 是 增 加 如 图 1.6 子 类 的 命 令 。

CLIPS>(clear)

CLIPS>(defclass UPPIE (is-a USER))

CLIPS>(defclass CHILD (is-a USER))

CLIPS>(defclass SUPPIE (is-a UPPIE))

CLIPS>(defclass MUPPIE (is-a UPPIE))

CLIPS>(defclass YUPPIE (is-a UPPIE))

CLIPS>(defclass PUPPIE (is-a YUPPIE))

CLIPS>(defclass YUKKIE (is-a YUPPIE CHILD))

定义类的顺序必须如上,一个类必须在他的子类之前被定义,如:

(defclass CHILD (is-a USER))

必须在下面之间输入:

(defclass YUKKIE (is-a YUPPIE CHILD))

当 你 试 图 在 CHILD 之 前 输 入 YUKKIE 类 时 , CLIPS 会 产 生 一 个 错 误 的 消 息 。

注 意 图 1.6 中 SUPPIE, MUPPIE 和 YUPPIE 自 左 到 右 的 出 现 顺 序 。 这

对 应 了 我 们 在 CLIPS 中 输 入 顺 序 的 惯 例 。你 同 样 可 以 看 到 为 什 么 CLILD 被

画 在 UPPIE 的 右 边 , 因 为 它 是 在 UPPIE 类 的 后 面 输 入 。
在 图 1.6 中 , 注 意 到 YUKKIE-YUPPIE 的 链 接 出 现 在 YUKKIE-CHILD

的左边。另一个惯例是,我们通常由图表中自左到右出现的顺序,来自左

到 右 书 写 优 先 表 中 的 直 接 超 类 。 YUKKIE 优 先 表 中 的 YUPPIE, CHILD 顺

序符合这种惯例。

• 查看

CLIPS 提 供 了 许 多 查 看 类 信 息 的 函 数 , 如 谓 语 函 数 , 用 来 检 测 一 个 类

是否为另一个类的超类或子类。

如 果 <class1>为 <class2>超 类 , superclassp 函 数 返 回 TRUE, 否 则 返 回

FALSE。 如 果 <class1>为 <class2>的 子 类 , subclassp 函 数 返 回 TRUR, 否 则

返 回 FALSE。 两 函 数 的 通 用 格 式 为 :

(function <class1><class2>)

举个例子:

CLIPS>(superclassp UPPIE YUPPIE)

TRUE

CLIPS>(superclassp YUPPIE UPPIE)

FALSE

CLIPS>(subclassp YUPPIE UPPIE)

TRUE

CLIPS>(subclassp UPPIE YUPPIE)

FALSE

CLIPS>

现 在 , 让 我 们 检 查 一 下 CLIPS 是 否 接 受 了 所 有 的 新 类 。 一 种 方 式 是 通
过 执 行 list-defclasses 命 令 。 下 面 是 该 命 令 的 输 出 :

CLIPS>(list-defclasses)

FLOAT

INTEGER

SYMBOL

STRING

MULTIFIELD

EXTERNAL-ADDRESS

FACT-ADDRESS

INSTANCE-ADDRESS

INSTANCE-NAME

OBJECT

PRIMITIVE

NUMBER

LEXEME

ADDRESS

INSTANCE

USER

INITIAL-OBJECT

UPPIE

CHILD

SUPPIE

MUPPIE

YUPPIE

PUPPIE

YUKKIE
For a total of 24 defcalsses.

CLIPS>

注 意 (list-defclasses)命 令 不 会 指 示 出 分 层 类 的 结 构 。 也 就 是 说 , 该 命 令 不 会 指

示出某个类是另外一个类的超类或子类。

如果你往该表的下面看,你会看到所有你输入的用户自定义类:

UPPIE,CHILD,YUPPIE,MUPPIE,SUPPIE,PUPPIE 和 YUKKIE。 除 了 已 经 讨

论 的 预 定 义 系 统 类 OBJECT 和 USER 外 , 还 有 许 多 其 他 的 预 定 义 类 。 你 应

该 像 熟 悉 前 几 章 所 学 的 CLIPS 类 一 样 知 道 它 们 的 名 字 。 CLIPS 的 预 定 义 类

型 同 样 也 被 作 为 类 来 定 义 , 这 样 它 们 就 可 以 使 用 COOL 了 。

图 1.7 为 CLIPS 参 考 指 南 中 的 一 般 继 承 图 表 , 里 面 的 箭 头 指 向 子 类 。

Fig.1.7 The Predefined CLIPS Classes

OBJECT 类 为 树 的 根 , 由 分 支 (branches)与 子 类 相 连 。 术 语 分 支 , 边

缘 (edge),链 接 和 弧 (arc)是 基 本 同 类 名 词 ,它 们 均 指 代 节 点 之 间 的 连 接 。每

个 子 类 比 它 的 超 类 要 低 一 级 。 类 由 层 级 (level)编 号 。 根 类 OBJECT 为 0 级 。

大 级 别 数 意 味 着 高 度 定 制 (specificity)。术 语 specificity 意 味 着 类 限 制

性 越 强 。举 个 例 子 ,LEXEME 为 SYMBOL 和 STRING 的 超 类 。如 果 你 知 道

一 个 对 象 是 LEXEME 类 , 那 么 你 就 知 道 它 只 可 能 是 SYMBOL 或 STRING。


然 而 , 如 果 一 个 对 象 是 SYMBOL, 那 么 它 不 可 能 是 STRING, 反 之 亦 然 。

因 此 , SYMBOL 和 STRING 类 要 比 LEXEME 更 特 殊 。

browse-classes 命 令 以 首 行 缩 进 格 式 显 示 了 类 的 层 级 。

CLIPS>(browse-classes)

OBJECT

PRIMITIVE

NUMBER

INTEGER

FLOAT

LEXEME

SYMBOL

STRING

MULTIFIELD

ADDRESS

EXTERNAL-ADDRESS

FACT-ADDRESS

INSTANCE-ADDRESS *

INSTANCE

INSTANCE-ADDRESS *

INSTANCE-NAME

USER

INITIAL-OBJECT

UPPIE

SUPPIE

MUPPIE

YUPPIE
PUPPIE

YUKKIE *

CHILD

YUKKIE *

CLIPS>

类名后面的星号指示它有多个超类。

(browse-classes)命 令 有 一 个 可 选 参 数 , 用 来 指 定 你 想 从 哪 个 类 开 始 查

看。这对于当你对表中一些其他类不感冒时,非常方便。下面的例子阐述

了 怎 样 显 示 图 1.6 中 部 分 YUPPIE 图 表 ,子 树 (subtrees)或 子 图 表 (subgraphs)

依据来自树或图表中的节点和链接。

CLIPS>(browse-classes UPPIE)

UPPIE

SUPPIE

MUPPIE

YUPPIE

PUPPIE

YUKKIE *

CLIPS>(browse-classes YUPPIE)

YUPPIE

PUPPIE

YUKKIE *

CLIPS>(browse-classes YUKKIE)

YUKKIE *

CLIPS>
• 有 一 个 抽 象 类 (abstract class)专 门 为 继 承 设 计 。抽 象 类 USER 不 能 有 直

属 实 例 (direct instance)为 之 定 义 ,而 是 作 为 实 例 直 接 定 义 给 类 。除 了 类 信 息 ,

类 的 优 先 继 承 也 被 表 示 。 这 是 一 个 顺 序 表 (ordered list), 从 顺 序 表 左 到 右 表

示 了 实 例 的 类 的 优 先 等 级 。继 承 优 先 列 出 了 所 有 类 到 根 类 OBJECT 的 超 类 。

同时,你也可以看到直接超类信息指示了这些超类为一个类的上部链接,

同时继承优先表显示了所有的超类。

即使一个类没有直接实例,如果它的子类有实例,则它也有直接实例。

一个类的非直接实例是所有它子类的实例。

一 个 具 体 的 类 (concrete class)被 允 许 拥 有 直 接 实 例 。 举 个 例 子 , 一 个 具

体 的 类 COW,它 的 直 接 实 例“ 基 站 ”可 能 是 著 名 的 电 视“ salescow”。从 抽

象类继承而来的普通类同样是抽象类。然而,从系统类继承而来的类,如

USER, 被 认 为 是 具 体 的 , 除 非 有 别 的 指 定 。 所 以 , UPPIE 和 YUPPIE 同 样

是具体的类。

强 烈 推 荐 你 在 CLIPS 中 定 义 的 所 有 类 都 为 USER 的 子 类 。 这 样 , CLIPS

将自动提供句柄用以打印,初始化和删除。

因 为 有 两 个 直 接 超 类 CHILD 和 YUPPIE,YUKKIE 类 有 多 种 继 承 性 。如

果你回想起组织中老板下的继承相似性,那么多重继承性将会产生一个有

趣的问题——谁才是老板?在树结构的例子中,每个类仅有一个直接超类

(boss), 这 样 就 可 以 简 单 的 统 计 出 谁 从 谁 哪 里 接 受 命 令 这 样 的 关 系 。 然 而 ,

在 多 重 继 承 的 例 子 中 ,出 现 了 多 个 具 有 同 等 职 权 的“ 老 板 ”,这 里 的“ 老 板 ”

为直接超类。

对 于 排 列 在 一 棵 树 结 构 中 的 类 来 说 ,也 就 是 单 继 承 ,继 承 关 系 简 单 ,所

有 类 按 照 继 承 路 径 回 溯 到 OBJECT。树 中 的 继 承 路 径 即 从 一 个 类 到 OBJECT

之间的最短路径。继承的概念同样适用于图表与子图表中。举个例子,子

图 UPPIE,SUPPIE,MUPPIE,YUPPIE 和 PUPPIE 的 优 先 继 承 是 棵 树 结 构 ,

因为每个子图仅有一个父图。这样,每个子图的优先继承关系即为回溯到

OBJECT 的 最 短 顺 序 。举 个 例 子 ,PUPPIE 的 优 先 继 承 为 PUPPIE,YUPPIE,


UPPIE, USER 和 OBJECT。

图 1.8 为 另 外 一 个 图 例 。 注 意 有 些 节 点 如 rhombus 有 多 个 父 节 点 。

为 了 简 化 ,我 们 将 在 本 书 中 仅 讨 论 单 层 继 承 。多 重 继 承 的 细 节 ,可 以 参

考 CLIPS 参 考 手 册 。

其他特性

类的一些其他有用函数如下所列:

函数 含义

ppdefclass 格式打印自定义类的内部结构

undefclass 清除类

describe-class 关于类的附加信息

class-abstractp 预定义函数,如果类为抽象类返

回 TRUE

如果你需要了解更多以上函数或本章中提到的函数的信息,请参看

CLIPS 参 考 手 册 。

• 第九章 富有意义的消息

取 悦 你 上 司 的 好 处 总 是 比 取 悦 你 的 下 属 要 多 。 ---鲍 文

在 本 章 中 ,你 将 学 到 许 多 关 于 类 和 对 象 调 用 实 例 的 方 法 。同 时 ,你 也 将

学到怎样利用槽值指定类的属性,怎样发送消息到对象。

OOP 特性

在 第 一 章 中 ,我 们 已 经 学 习 了 继 承 的 基 本 观 点 。OOP 中 的 继 承 之 所 以 如

此 重 要 , 缘 自 于 : 继 承 允 许 了 定 制 软 件 (customized software)的 最 简 结 构 。

对 于“ 定 制 软 件 ”,我 们 指 的 并 不 是 胡 乱 拼 凑 起 来 的 软 件 ,而 是 ,它 接 近 于
带有批量项目并为特殊应用而修改的那种。批量项目可以被认为是一种软

件 工 厂 (software factory)式 的 产 品 , 它 可 以 快 速 、 经 济 并 可 靠 的 生 成 一 般 形

式的项目,这意味着方便定制。

OOP 范 式 的 核 心 是 建 立 起 具 有 级 联 的 类 ,以 更 简 便 快 速 ,可 靠 的 开 发 出

新 的 软 件 。通 常 情 况 下 ,新 的 软 件 都 是 在 现 有 软 件 基 础 上 做 些 修 改 ,这 样 ,

程序员就不必总是重复循环已有的工作了。

尽 管 在 过 去 ,人 们 便 已 经 开 始 尝 试 提 供 可 再 利 用 代 码 ,通 过 将 这 些 代 码

作 为 子 程 序 库 的 原 理 , 计 算 机 语 言 中 的 纯 粹 OOP(pure OOP), 如 smalltalk

利用系统中所有的软件都作为再生代码,将这个概念应用到它的逻辑结论

中 。 在 smalltalk 中 , 所 有 的 都 是 对 象 ,甚 至 包 括 类 。 在 CLIPS 中 ,原 始 类

型 的 实 例 ,如 NUMBER,SYMBOL,STRING 等 等 ,还 有 像 用 户 自 定 义 的 实 例

都 是 对 象 。在 CLIPS 中 ,类 不 是 对 象 。举 个 例 子 ,NUMBER 类 型 的 1,SYMBO

类 型 的 Duck,STRING 类 型 的“ Duck”,和 用 户 自 定 义 实 例 Duck 都 是 对 象 。

OOP 范 式 与 子 程 序 库 非 常 之 不 同 ,在 于 子 程 序 代 码 中 的 片 段 语 句 可 以 被

使 用 , 也 可 以 不 被 使 用 , 这 取 决 于 程 序 员 一 时 的 突 发 奇 想 。 OOP 范 式 鼓 励

和支持模块代码——即消息句柄——这样方便于修改和维护。随着系统规

模和花费的增加,这种可维护性代码的特点发挥着越来越重要的作用。

OOP 中 的 类 就 像 是 一 个 软 件 工 厂 (software factory), 它 包 含 了 关 于 对 象

的 设 计 信 息 。换 句 话 说 ,类 就 是 一 个 模 板 ,它 可 以 被 用 来 产 生 相 同 的 对 象 ,

这些对象是类的实例。最经典的比如是,类好比一头奶牛的蓝图,对象可

以 产 生 牛 奶 , 如 Elsie, 便 是 实 例 。

实 例 名 的 通 用 语 法 为 一 个 被 方 括 号 — — []括 住 的 简 单 字 符 。 如 下 所 示 :

[<name>]

上 面 的 方 括 号 事 实 上 并 不 是 实 例 名 的 一 部 分 ,它 只 是 一 个 字 符 ,如 Elsie。

方括号被用来括住实例名,以防止在使用实例名时出现的模棱两可。这也
可 以 出 现 在 (send)函 数 中 。在 有 模 糊 不 清 的 情 况 下 ,使 用 方 括 号 ,有 百 益 而

无一害。

CLIPS 中 对 象 的 一 些 不 同 类 型 指 示 如 下 :

对象 类

Dorky_Duck SYMBOL

“ Dorky_Duck” STRING

1.0

FLOAT

INTEGER

(1 1.0 Dorky_Duck “ Dorky_Duck” ) MULTIFIELD

<Pointer:00AB12CD> EXTERNAL-ADDRESS

[Dorky_Duck] DUCK

类 SYMBOL,STRING,FLOAT,INTEGER 和 MULTIFIELD 拥 有 相 同 的 名

字 , 这 些 你 已 经 在 CLIPS 的 基 于 规 则 编 程 中 熟 悉 了 。 这 些 被 称 为 原 始 对 象

类 型 (primitive object types), 因 为 它 们 由 CLIPS 提 供 并 且 按 照 需 求 自 动 维

护 。 这 些 原 始 类 型 主 要 被 提 供 用 在 类 函 数 (generic functions)中 。 NUMBER

为 复 合 类 ,它 可 以 是 FLOAT,也 可 以 是 INTEGER,LEXEME 可 以 是 SYMBOL

或 STRING。 复 合 类 的 提 供 是 为 了 方 便 数 字 和 字 符 类 型 的 匹 配 。

相 比 之 下 ,用 户 自 定 义 对 象 类 型 是 那 些 通 过 用 户 自 定 义 类 产 生 的 。如 果

你 回 溯 到 第 一 章 1.7 图 中 的 预 定 义 CLIPS 类 , 你 会 发 现 , 原 始 类 和 自 定 义

类 在 CLIPS 中 都 是 类 的 最 高 阶 层 分 类 。

两 个 函 数 转 换 一 个 字 符 到 一 个 实 例 名 , 反 之 亦 然 。

Symbol-to-instance-name 转 换 一 个 字 符 到 实 例 名 , 如 下 所 示 :
CLIPS>(clear)

CLIPS>(symbol-to-instance-name Dorky_Duck)

[Dorky_Duck]

CLIPS>(symbol-to-instance-name (sym-cat Dorky “ _” Duck))

[Dorky_Duck]

CLIPS>

注 意 CLIPS 标 准 函 数 如 (sym-cat)怎 样 将 两 个 部 分 连 结 起 来 , 可 以 同

CLIPS 的 对 象 系 统 一 起 使 用 。

反 函 数 — — instance-name-symbol, 将 一 个 实 例 名 转 换 为 一 个 字 符 , 如

下所示:

CLIPS>(instance-name-to-symbol [Dorky_Duck])

Dorky_Duck

CLIPS>(str-cat (instance-name-to-symbo [Dorky_Duck]) “ is a DUCK” )

“ Dorky_Duck is a Duck”

CLIPS>

其他特性

OOP 与 Nature 有 所 不 同 。在 Nature 中 ,对 象 仅 仅 只 能 类 似 对 象 复 制 ,

就 像 鸟 和 蜜 蜂 (鸡 和 蛋 不 属 于 此 类 规 则 中 )。 然 而 , 在 OOP 中 , 实 例 只 能 由

类 模 板 创 建 。在 纯 OOP 系 统 中 ,如 Smalltalk,指 定 类 的 对 象 由 发 送 相 关 消

息 到 类 而 创 建 。 事 实 上 , OOP 的 核 心 包 含 可 以 发 送 许 多 不 同 类 型 的 消 息 从

一个对象到另一个的特性,甚至可以从一个对象到其本身。

为了查看消息的工作机制,让我们输入以下命令创建一个自定义

DUCK 类 , 并 检 查 是 否 已 经 被 输 入 。 注 意 : DUCK 类 并 没 有 指 定 角 色 (role)

描 述 符 。 如 果 一 个 类 有 具 体 化 角 色 (role concrete), 类 的 直 接 实 例 便 能 被 创
建 。 如 果 角 色 没 有 被 指 定 , CLIPS 依 据 继 承 来 确 定 角 色 。 在 继 承 决 定 角 色

中 , 系 统 类 起 着 具 体 类 的 作 用 。 通 过 缺 省 值 , 任 何 从 USER 继 承 的 类 都 是

具体类,并且不必被声明作为允许直接实例的创建。

如 果 一 个 类 拥 有 抽 象 角 色 (role abstract), 那 么 它 的 非 直 接 实 例 能 被 创

建 。抽 象 类 仅 仅 只 能 作 为 继 承 目 的 来 被 定 义 。举 个 例 子 ,一 个 命 名 为 PERSON

的抽象类能被定义如名字,地址,年龄,身高,体重等属性,这些均继承

自 具 体 类 MAN 和 WOMAN。MAN 的 一 个 直 接 实 例 可 以 是 一 个 叫 Harold 的

男 人 , WOMAN 的 一 个 直 接 实 例 可 以 是 一 个 叫 Henrietta 的 女 人 。

CLIPS>(defclass DUCK (is-a USER))

CLIPS>(describe-class DUCK)

------------------------------------------------------------------------------------------------

------------------------------------------

*******************************************************************

**

Concrete: direct instances of this class can be created.

Reactive: direct instances of this class can match defrule

patterns.

Direct Superclasses: USER

Inheritance Precedence: DUCK USER OBJECT

Direct Subclasses:

---------------------------------------------------------------------

Recognized message-handlers:

init primary in class USER

delete primary in class USER

create primary in class USER


print primary in class USER

direct-modify primary in class USER

message-modify primary in class USER

direct-duplicate primary in class USER

message-duplicate primary in class USER

*******************************************************************

**

------------------------------------------------------------------------------------------------

------------------------------------------

CLIPS>

因 为 在 CLIPS 中 ,类 不 是 对 象 ,所 以 我 们 不 能 发 送 消 息 来 创 建 一 个 对

象 。 取 而 代 之 的 是 , make-instance 函 数 可 以 被 用 来 创 建 一 个 实 例 对 象 。 基

本语法如下所示:

(make-instance [<instance-name>] of <class><slot-override>)

通 常 情 况 下 , 可 以 指 定 实 例 名 。 然 而 , 如 果 你 没 有 指 定 实 例 名 , CLIPS 会 利 用

gensym*函 数 产 生 , 同 时 槽 值 也 会 被 指 定 。

现在,我们拥有一间鸭子工厂,让我们按如下步骤创建一些实例,实

例 名 由 大 括 号 括 住 ,注 意 使 用“ of”关 键 字 区 分 实 例 名 和 类 名 。你 必 须 包 含

“ of”或 者 将 会 得 到 一 个 语 法 错 误 的 结 果 。同 样 ,注 意 代 码 中 的 大 括 号 意 味

着 实 例 名 , 当 元 句 法 中 的 大 括 号 , 如 (make-instance), 意 思 是 可 选 。

CLIPS>(make-instance [Dorky] of DUCK)

[Dorky]

CLIPS>(make-instance [Elsie] of COW)


[PRNTUTTL1] Unable to find class COW.

CLIPS>(make-instance Dorky_Duck of DUCK)

[Dorky_Duck]

CLIPS>(instances)

[initial-object] Of INITIAL-OBJECT

[Dorky] of DUCK

[Dorky_Duck] of DUCK

For a total of 3 instances.

CLIPS>

• 删除实例

尽 管 一 个 (reset)命 令 可 以 删 除 除 了 [initial-instance]外 所 有 的 实 例 , 同 时

也 可 以 用 (definstances)命 令 来 创 建 新 的 实 例 。 如 果 你 想 永 久 的 删 除 一 个 实

例 ,那 么 你 可 以 试 试 (unmake-instance)函 数 ,它 可 以 删 除 一 个 或 所 有 的 实 例 ,

删 除 时 需 指 定 参 数 以 确 定 哪 条 该 被 删 除 , 如 果 删 除 所 有 的 , 则 用 “ *”。

下 面 即 为 (unmake-instance)命 令 的 例 子 :

CLIPS>(unmake-instance *) ;删 除 所 有 的 实 例

TRUE

CLIPS>(instance) ;验 证 下 是 否 全 部 删 除

CLIPS>(reset) ;重 新 创 建 新 的 实 例

CLIPS>(instances) ;验 证 新 实 例 的 创 建

[initial-object] of INITIAL-OBJECT

[Dorky] of DUCK

[Dorky_Duck] of DUCK

For a total of 3 instances .

CLIPS>(unmake-instance [Dorky]) ;删 除 一 个 指 定 的 实 例
TRUE

CLIPS>(instances)

[initial-object] of INITIAL-OBJECT

[Dorky_Duck] of Duck

For a total of 2 instances .

CLIPS>

另 一 个 删 除 指 定 实 例 的 方 法 是 发 送 (send)一 个 删 除 (delete)消 息 。(send)函 数 的 基

本语法如下所示:

(send [<instance-name>] <message>)

·一 条 命 令 中 只 能 有 一 个 实 例 名 被 指 定 , 如 果 是 一 个 自 定 义 实 例 名 , 则 必 须 由

中括号包含。

举 个 例 子 , 下 面 的 例 子 将 使 实 例 Dorky_Duck 消 失 。

CLIPS>(reset) ;重 新 创 建 实 例

CLIPS>(instances) ;验 证 新 创 建 的 实 例

[initial-object] of INITIAL-OBJECT

[Dorky] of DUCK

[Dorky_Duck] of DUCK

For a total of 3 instances .

CLIPS>(send [Dorky_Duck] delete)

TRUE

CLIPS>(instances)

[initial-object] of INITIAL-OBJECT

[Dorky] of DUCK
For a total of 2 instances .

CLIPS>

在 (send)函 数 使 用 “ *” 是 无 效 的 ,“ *” 只 有 在 (unmake)函 数 中 才 起 作 用 。 另 一

个 办 法 是 你 可 以 定 义 自 己 的 句 柄 用 以 删 除 ,这 样 系 统 可 以 接 受“ *”允 许 你

这 样 删 除 实 例 : (send [instance-name] my_delete *)。

(send)消 息 仅 仅 只 有 依 靠 有 合 适 句 柄 的 目 标 对 象 (target object)才 能 执 行

行 为 。CLIPS 自 动 为 每 个 自 定 义 类 提 供 如 print,init,delete 等 等 句 柄 。认 识 到

消 息 (send [Dorky_Duck] delete) 只 在 该 实 例 是 自 定 义 类 下 才 起 作 用 非 常 重

要 。如 果 你 定 义 一 个 不 是 继 承 于 USER 的 类 ,如 INTEGER 的 子 类 ,那 么 你

必 须 也 要 创 建 合 适 的 句 柄 来 实 现 你 的 设 计 要 求 , 如

printing,creating,deleteing 实 例 。 定 义 USER 的 子 类 , 并 利 用 系 统 提 供 句 柄

的优点则更容易些。

• 消息函数的作用

(send)函 数 是 OOP 的 核 心 ,因 为 它 是 对 象 间 关 联 的 唯 一 合 适 途 径 。通 过

对象封装原理,一个对象可以通过发送消息来获取另一对象的数据。

举 个 例 子 ,如 果 某 人 想 知 道 你 早 餐 吃 了 些 什 么 ,他 们 通 常 会 问 你 ,比 如

发一个信息给你。一个不太礼貌的回应或许是突然张开你的嘴巴,耸动一

下你的喉结。如果对象的封装原理不起作用,任何对象对于其他对象的私

有部分将毫无意义,这是个潜在的灾难性后果。

(send)函 数 的 一 个 非 常 有 用 的 应 用 是 用 来 打 印 对 象 的 信 息 。到 目 前 为 止 ,

你看到所有对象的例子都没有结构。然而,就像自定义模板赋予一个规则

模式以结构,槽给出了对象的结构。对于自定义模板和对象,槽作为一个

被命名的位置用来存储数据。然而,不同于自定义模板的槽,对象通过类

来获取它们的槽,类应用了继承。这样,对象槽的信息就可以通过对象的

子 类 来 高 效 的 被 继 承 了 。 无 界 (unbound)槽 没 有 分 配 槽 值 。 所 有 的 槽 都 是 有
界的。

作 为 一 个 简 单 的 例 子 ,让 我 们 来 定 义 一 个 对 象 ,通 过 槽 来 存 储 人 的 信 息 ,

并 向 它 发 送 消 息 。下 面 的 命 令 将 首 先 设 置 CLIPS 环 境 到 恰 当 的 结 构 。Sound

槽 和 age 槽 起 初 并 没 有 包 含 任 何 数 据 , 如 nil 值 。

CLIPS>(clear)

CLIPS>(defclass DUCK (is-a USER)

(slot sound)

(slot age))

CLIPS>(definstances DORK_OBJECTS

(Dorky_Duck of DUCK))

CLIPS>(reset)

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound nil)

(age nil)

CLIPS>(send [Dorky_Duck] put-sound quack)

quack

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of Duck

(sound quack)

(age nil)

CLIPS>

上面的槽以类中定义的顺序被打印出来。然而,如果实例从多个类中继承槽,

来自于通用类的槽则将会优先打印。

可 以 通 过 put-message 来 改 变 槽 值 。 默 认 状 态 下 , CLIPS 为 每 个 类 的 槽
创 建 了 一 个 put-句 柄 来 获 取 put-message。 注 意 put 后 面 的 破 折 号 , 它 是 该

消 息 句 法 的 重 要 组 成 部 分 , 因 为 它 将 put 和 槽 名 分 开 。 (send)函 数 里 只 能 包

含 一 个 “ put-”。 因 此 , 如 果 要 改 变 多 槽 或 一 些 实 例 的 相 同 槽 时 , 你 必 须 发

送 多 个 消 息 。 你 可 以 通 过 写 一 个 多 发 送 函 数 , 或 使 用 modify-instance 函 数

来代替上面的手动操作。

在 make-instance 中 , 可 以 通 过 slot-override 来 设 置 槽 值 。 示 例 如 下 :

CLIPS>(make-instance Dixie_Duck of DUCK (sound quack) (age 2))

[Dixie_Duck]

CLIPS>(send [Dixie_Duck] print)

[Dixie_Duck] of DUCK

(sound quack)

(age 2)

CLIPS>

“ Put-” 的 补 充 消 息 是 “ get-”, 用 来 获 取 槽 中 的 数 据 , 将 会 在 下 一 个 例

子 中 体 现 出 来 。 当 put-成 功 执 行 后 将 会 返 回 新 的 值 , 当 get-被 成 功 执 行 时 ,

将 返 回 恰 当 的 值 。如 果 put-和 get-没 有 成 功 执 行 时 ,将 会 返 回 一 个 出 错 消 息 。

下面的例子将演示它们的用法。

CLIPS>(send [Dorky_Duck] put-color white) ;没 有 color 槽

[MSGFUN1] No applicable primary message -handlers found for put-color.

FALSE

CLIPS>(send [Dorky_Duck] get-age)

nil

CLIPS>(send [Dorky_Duck] put-age 1) ;age 获 取 值

1
CLIPS>(send [Dorky_Duck] get-age) ;验 证 值 为 正 确

CLIPS>

对 比 与 put-消 息 ,get-消 息 返 回 一 个 槽 的 槽 值 。get-的 值 被 返 回 后 ,可 以

通过使用其他的函数,分配其一个变量等等。相比之下,一个被打印出来

的值不能被用于其他函数,分配变量等等,因为该值已发送到标准输出设

备了。一个规避该问题的方法是将值打印到一个文件,然后读出该文件内

的数据。虽然这不是一个很好的解决方案,当它确实能解决问题。另外一

个 方 法 就 是 重 新 写 一 个 打 印 消 息 句 柄 (print message-handler), 同 样 返 回 值 。

关 于 槽 的 一 个 重 要 特 点 是 ,不 能 通 过 增 加 ,删 除 或 改 变 槽 的 特 性 来 修 改

一 个 类 的 槽 。唯 一 改 变 类 的 方 法 是( 1)删 除 类 的 所 有 实 例 ,
( 2)使 用 (defclass)

来定义一个相同名字的类并所想要的槽。这个方法类似于通过载入一个相

同名字的新的规则来修改规则。

• 类程式

现 在 , 你 已 经 学 习 了 槽 和 实 例 , 下 面 让 我 们 来 熟 悉 类 程 式 (class

etiquette)。 这 里 的 术 语 — — 规 则 , 指 的 是 一 套 做 事 情 的 指 导 方 针 。

与 标 准 的 程 序 设 计 相 比 , OOP 范 式 是 类 导 向 (class oriented)的 。 每 个 对

象 都 与 类 有 内 在 的 联 系 , 这 些 类 是 类 等 级 的 一 部 分 。 OOP 程 序 员 关 注 的 是

所 有 的 类 或 类 的 结 构 层 次 (class architecture)和 对 象 之 间 消 息 的 传 递 ,而 不 是

首先考虑执行。因此,在普通的程序设计语言中,执行是显式表示的,而

在 OOP 中 为 隐 式 。两 种 方 式 的 结 果 却 是 相 同 的 。不 过 ,OOP 系 统 能 更 简 单

的被验证和维护。

正确使用类的方法总结在下面三条规则中了:

类程式用法
1. 类 的 级 别 分 层 必 须 使 用 is-a 链 接 而 被 指 定 逻 辑 的 增 加 。

2. 如果一个类只包含一个实例,则这个类就是多余的。

3. 类的命名不能是实例名,反之亦然。

以上第一条规则限制了你的程序中单个类的创建。如果没有该限制,那么

你 也 许 根 本 就 不 需 要 OOP。 随 着 类 被 创 建 的 递 增 , 你 可 以 更 简 单 的 验 证 和

维护你的代码。另外,增加的类链能更容易的被添加到类库中,以更大程

度的方便促进新代码的生成。类库的概念与执行子程序库非常类似。只有

is-a 链 接 能 被 使 用 , 因 为 它 是 CLIPS6.3 版 本 提 供 的 唯 一 联 系 了 。

第二条规则促进了以类作为一个模板去产生多个同类对象方式的实现,

当然,你也可以从零或一个实例开始去创建。然而,如果你只会用到一个

类中的一个实例,那么你可以考虑修改它的超类以适应该实例,而不要定

义一个新的子类。如果你所有的类都仅有一个实例,那么你的程序也许过

于 简 单 ,用 OOP 不 能 很 好 体 现 它 的 长 处 ,你 的 代 码 用 程 序 化 语 言 写 或 许 更

好。

第 三 条 规 则 的 意 思 是 ,类 不 能 命 名 为 一 个 实 例 的 名 ,反 之 亦 然 ,以 消 除

歧义。

其他特性

这 里 还 有 许 多 关 于 槽 的 有 用 函 数 。如 果 你 使 用 这 些 函 数 去 测 试 函 数 值 的

合 法 性 , 那 么 你 的 程 序 将 会 更 严 密 。 通 常 一 个 程 序 如 果 不 是 返 回 TRUE 就

是 返 回 FALSE。

函数 作用

class-slot-existp 如 果 类 槽 存 在 , 则 返 回 TRUE

slot-existp 如果实例槽存在,则返回

TRUE
slot-boundp 如 果 指 定 的 槽 包 含 一 个 值 ,则 返

回 TRUE

instance-address 返回指定存储实例的物理地址

instance-name 返回给定地址的名,反之亦然

instancep 如 果 它 的 参 数 是 实 例 ,则 返 回

TRUE

instance-addressp 如果参数是实例的地址,则返

回 TRUE

instance-namep 如果参数是实例名,则返回

TRUE

list-definstances 输出所有自定义实例

ppdefinstances 格式打印自定义实例

watch instances 允许查看和删除被创建的实

unwatch instances 关闭查看实例

save-instances 保存实例到文件

load-instances 加载实例到文件

undefinstances 删除命名的自定义实例

• 第十章 奇妙的槽面

如果你想拥有类,那么就像是和老朋友一样,去互动,去尝试对话。

在 本 章 中 , 你 将 学 到 许 多 关 于 槽 和 利 用 一 些 槽 面 (facets)来 指 定 它 们 属

性的方法。就像槽用来描述实例一样,槽面用来描述槽。槽面的使用是非

常 好 的 软 件 工 程 技 术 , 它 对 于 CLIPS 而 言 避 免 了 非 法 值 的 插 入 , 从 而 降 低

了运行时错误或崩溃的可能性。有许多种槽面用来指定槽,总结在下面的
表格中。

槽面名 含义

default and default-dynamic 为槽设定初始值

cardinality 多槽的个数

access type 读-写,只读,只初始化访问

storage 实例中的本地槽或类中的共享槽

propagation 继承或非继承槽

source 复合的或独占式继承

documentation 文件或槽

override-message 消除槽的显示信息

create-accessor 创建 put-和 get-句柄

visibility 仅公有或私有的定义类

reactive 槽触发器模式匹配的改变

由 于 版 面 的 关 系 ,本 章 中 ,我 们 将 仅 详 细 讲 解 上 面 诸 多 槽 面 中 的 一 小 部 分 。

了 解 更 多 的 细 节 , 可 以 参 看 CLIPS 参 考 指 南 (CLIPS Reference Manual).

Default 槽

当 一 个 实 例 被 创 建 或 初 始 化 后 ,default facet 用 来 设 置 槽 的 默 认 值 ,如 下

例所示:

CLIPS>(clear)

CLIPS>(defclass DUCK (is-a USER)

(slot sound (default quack))

(slot ID)

(slot sex (default male)))

CLIPS>(make-instance Dorky_Duck of DUCK)


[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack)

(ID nil)

(sex male)

CLIPS>

正 如 你 所 见 ,sex 槽 和 sound 槽 通 过 default facet 设 定 了 默 认 值 。通 过 default

关 键 字 ,任 意 有 效 的 CLIPS 表 达 式 都 可 以 不 用 包 含 一 个 变 量 值 。举 个 例 子 ,

sound 槽 的 默 认 表 达 式 为 字 符 quack。槽 面 表 达 (facet expression)也 可 以 用 到

函数,这将在下一个例子中呈现。

Default 槽 面 是 静 态 的 默 认 (static default),因 为 槽 面 表 达 式 的 值 在 类 被 定

义且无任何改变,除非被重新定义时已经被限定了。举个例子,我们来设

定 ID 槽 的 默 认 值 为 gensym*函 数 , 该 函 数 用 来 返 回 一 个 新 值 , 该 值 不 为 系

统每次调用。

CLIPS>(clear)

CLIPS>(defclass DUCK (is-a USER)

(slot sound (default quack))

(slot ID (default (gensym*)))

(slot sex (default male)))

CLIPS>(make-instance [Dorky_Duck] of DUCK) ;Dorky_Duck #1

[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack)
(ID gen1)

(sex male)

CLIPS>(make-instance [Dorky_Duck] of DUCK) ; Dorky_Duck #2

[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack)

(ID gen1)

(sex male)

CLIPS>

如 你 所 见 ,ID 一 直 保 持 为 gen1,这 是 因 为 (gensym*)仅 被 定 值 一 次 ,即 使 在

第 二 个 实 例 被 创 建 时 ,也 不 会 重 新 定 值 。注 意 ,如 果 你 已 经 调 用 过 (gensym*),

那 么 (gensym*)值 在 你 的 电 脑 里 显 示 可 能 不 是 如 此 , 因 为 它 每 次 被 调 用 后 会

自 动 递 增 , 并 不 会 被 (clear)命 令 消 解 。 只 有 当 你 重 启 CLIPS 时 , (gensym*)

函数才会被重新设置到初始值。

现 在 ,假 设 我 们 想 留 意 一 下 已 经 创 建 的 Dorky_Duck 实 例 的 改 变 。与 其 使 用

静 态 默 认 (static default), 我 们 可 以 用 使 用 默 认 动 态 (default dynamic)槽 面 ,

它 可 以 在 每 一 个 新 实 例 被 创 建 时 ,为 槽 面 表 达 式 定 值 。注 意 下 面 一 个 例 子 ,

看看与前面的例子有什么不同。

CLIPS>(clear)

CLIPS>(defclass DUCK (is-a USER)

(slot sound (default quack))

(slot ID (default-dynamic (gensym*)))

(slot sex (default male)))

CLIPS>(make-instance [Dorky_Duck] of DUCK) ; Dorky_Duck #1


[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack)

(ID gen2)

(sex male)

CLIPS>(make-instance [Dorky_Duck] of DUCK) ;Dorky_Duck #2

[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack)

(ID gen3) ;注 意 这 里 的 ID 不 同 于 Dorky_Duck #1

(sex male)

CLIPS>

上 面 的 例 子 中 使 用 了 动 态 默 认 (dynamic default), 第 二 个 实 例 的 ID 是

gen3, 与 第 一 个 实 例 的 gen2 不 同 。 比 较 而 言 , 前 一 个 使 用 静 态 默 认 (static

default)的 例 子 中 , ID 的 值 都 是 相 同 的 gen1, 因 为 在 类 被 定 义 , (gensym*)

只 会 被 定 值 一 次 , 而 在 后 者 动 态 默 认 (dynamic default)中 每 个 新 实 例 被 创 建

时 , (gensym*)定 值 自 动 增 加 。

• 重要特性

槽 的 集 合 引 用 两 种 类 型 字 段 之 一 ,这 两 种 类 型 是 :( 1)单 字 段 ,( 2)多

字 段 。术 语 cardinality 指 代 一 个 集 合 。有 界 单 字 段 槽 仅 包 含 一 个 字 段 ,有 界

多 字 段 槽 可 包 含 零 或 多 个 字 段 。每 个 有 界 单 字 段 槽 和 多 字 段 槽 包 含 一 个 值 ,

然 而 ,一 个 多 字 段 值 也 许 包 含 多 个 字 段 。举 个 例 子 ,(a b c)是 一 个 单 一 的 多

字 段 值 ,该 字 段 值 包 含 三 个 字 段 。 空 字 符 串 “ ” 是 一 个 单 字 段 值 ,就 像 “ a

b c” 一 样 。 如 此 相 反 , 一 个 无 界 槽 没 有 值 。
与 单 、多 字 段 变 量 相 似 ,请 将 槽 想 像 成 你 的 信 箱 ,你 有 时 会 收 到 单 独 的

一封没有信封的垃圾邮件,地址标签却被粘贴在一叠纸上,上面写好了寄

往 “ 住 址 ”。 其 他 时 候 , 你 也 许 会 在 信 箱 里 找 到 一 封 上 面 写 多 个 地 址 的 信

封。没有信封的单张垃圾邮件就像是单字段值,多个地址的信封就像是多

字段值。如果垃圾邮件发送者发送给你一封信,里面什么都没有,那么这

可 对 应 一 个 空 多 槽 字 段 变 量 。( 试 想 ,如 果 垃 圾 邮 件 是 空 的 ,那 么 你 是 否 算

得上收到了垃圾邮件?)

多 槽 面 (multiple facet)关 键 字 为 mulitslot,被 用 来 存 储 多 字 段 变 量 ,如 下

例所示:

CLIPS>(clear)

CLIPS>(defclass DUCK (is-a USER)

(multislot sound (default quack quack)))

CLIPS>(make-instance [Dorky_Duck] of DUCK)

[Dorky_Duck]

CLIPS>(send [Dorky_Duck] print)

[Dorky_Duck] of DUCK

(sound quack quack)

CLIPS>

多 字 段 值 可 以 被 利 用 来 使 用 get-和 put-, 如 下 例 所 示 , 下 面 例 子 显 示 了

怎 样 联 系 quacks。

CLIPS>(send [Dorky_Duck] put-sound quack1 quack2 quack3)

(quack1 quack2 quack3)

CLIPS>(send [Dorky_Duck] get-sound)

(quack1 quack2 quack3)


CLIPS>

标 准 CLIPS 函 数 ,如 nth$,被 用 来 获 取 多 字 段 值 的 第 n 个 字 段 。下 面 的

例 子 显 示 了 怎 样 选 取 一 个 确 定 的 quack。

CLIPS>(nth$ 1 (send [Dorky_Duck] get-sound))

quack1

CLIPS>(nth$ 2 (send [Dorky_Duck] get-sound))

quack2

CLIPS>(nth$ 3 (send [Dorky_Duck] get-sound))

quack3

CLIPS>

You might also like