2004.11.3 AI程序设计 1
第 二 部分:第 7章 Visual Prolog编程
第 7章 Visual Prolog编程
本章介绍基于 Visual Prolog编程方面的知识,
主 要 内 容 包 括 Visual Prolog 基础, Visual
Prolog的 GUI编程, Visual Prolog的逻辑层,
Visual Prolog的数据层 。
2004.11.3 AI程序设计 2
第 二 部分:第 7章 Visual Prolog编程
第 7章 Visual Prolog编程
7.1 Visual Prolog基础
7.2 Visual Prolog的 GUI编程
7.3 Visual Prolog的逻辑层
7.4 Visual Prolog的数据层
本章小结与习题
2004.11.3 AI程序设计 3
第 二 部分:第 7章 Visual Prolog编程
7.1 Visual Prolog基础
传统的 Prolog与 Visual Prolog 6之间的差别主要体现在如下几个方面,
1)程序结构
很明显, 传统 Prolog中所使用的结构与 Visual Prolog 6中使用的结构, 在理
解的难易程度方面不同 。 主要包括如何规划来自定义 ( definitions) 的声明
( declarations), 以及如何简要地说明程序必须使用指定关键字
( keywords) 进行查找的主目标 Goal。
2)文件考虑
Visual Prolog 6提供了各种工具, 以便将程序结构组织成不同类型的文件 。
3)作用域访问
Visual Prolog 6可以挑选在其他模块中通过使用称为作用域标识 ( Scope
identification) 的概念而开发出来的功能 。
4)面向对象
Visual Prolog 6程序可以编写成面向对象的程序, 使用标准的面向对象特性 。
2004.11.3 AI程序设计 4
第 二 部分:第 7章 Visual Prolog编程
7.1 Visual Prolog基础
7.1.1 程序结构
7.1.2 目标 Goal
7.1.3 文件考虑
7.1.4 作用域访问
7.1.5 面向对象
7.1.6 一个完整的例子,family1.prj6
7.1.7 程序的取舍
7.1.8 小结
2004.11.3 AI程序设计 5
第 二 部分:第 7章 Visual Prolog编程
7.1.1 程序结构
Visual Prolog的程序, 从结构上讲, 主要包括若干个段, 即论
域段, 谓词段, 子句段, 目标段等 。 Visual Prolog作为强类型的
编译型语言, 通常用论域段和谓词段来给出有关的声明或定义 。
2004.11.3 AI程序设计 6
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
声明 (Declaration)与定义 (Definition)有着不同的含义 。
在 Prolog中, 当需要使用一个谓词的时候, 就可以直接使用, 无需事
先向 Prolog 推理机做任何的通告 。 例如, 在 前 面 的 例 子 中,
grandFather谓词的子句就是利用传统的 Prolog的谓词头和谓词体结构
直接写下来的 。 我们不用在代码中再告知推理机这个谓词结构是后面需
要使用的 。
类似地, 当在传统的 Prolog中使用一个复合论域时, 无须首先告诫
Prolog推理机关于使用该论域有何意图 。 只要感到需要, 就可以直接使
用一个论域 。
然而, 在 Visual Prolog 6中, 在编写一个谓词的子句体代码之前,
我们必须首先对编译器声明这样一个谓词的存在 。 类似地, 在使用任何
论域之前, 也必须先进行声明, 以便将这些论域的存在告知编译器 。
2004.11.3 AI程序设计 7
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
在 Visual Prolog 6中需要这种预先告知功能的原因本质上是为了保
证将运行时间异常 ( running exceptions) 尽可能地转变为编译时间错
误 ( compile time errors) 。
对于, 运行时间异常,, 我们指的是只在运行所编译的程序期间出现
的问题 。 例如, 如果我们想使用一个整数作为一个函数的参数, 但是却
错误地使用了实数, 这就会成为一个运行错误 ( 这大都出现在使用其他
编译器的程序中, 但不是在 Visual Prolog 6中 ), 程序就会因此而失败 。
当声明已定义的谓词和论域时, 这类位置语法, 即哪一个参变量属于
哪一个论域, 就会对编译器起作用 。 因此, 当 Visual Prolog 6 执行编
译时, 它将比较彻底的检查程序, 以发现诸如此类的语法错误及其它错
误 。
2004.11.3 AI程序设计 8
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
由于 Visual Prolog 6 的这些特性, 整个程序的效率因此提高了 。
程序员不必等到程序实际执行时才发现错误 。 事实上, 对于实际编写
程序的人, 将体会到这大大地节约了时间 。 通常, 运行时导致发生运
行时间异常的条件如此难以捉摸, 以致于错误可能会在很多年后才被
发现, 或者会在许多特别重要的情况或令人尴尬的场合表现出来 。
所有这些表明, 编码中存在的论域和谓词要在定义前给出合适的声
明, 以给编译器详尽的指示 。
2004.11.3 AI程序设计 9
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
一个 Visual Prolog 6 的程序包括一组被标点分为不同部分的代码,
由特定的关键字告诉编译程序所要生成的类型 。 例如, 关键字可以将谓
词和论域的定义和声明区分开 。 通常, 每一部分由一关键字开始, 在每
一部分结束时, 一般没有关键字指示 。 新的关键字的出现表明前一部分
的结束和下一部分的开始 。
对这一规则的例外是关键字, implement”和, end implement”,
在这两个关键字中间的代码表示它们属于一个特殊的类 。 若有人不懂类
的概念, 可以把它看作程序的一个模块或一个部分 。
在本节中, 我们将只介绍下述关键字 。 同样我们给出了这些关键字的
用途, 具体的句法可以从文档资料中学到 。 Visual Prolog 6 中还有其它
一些关键字, 可以在以后的内容和文档资料中学到 。
本章需要掌握的关键字在下面分别描述 。
2004.11.3 AI程序设计 10
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
Implement和 end implement
在这里讨论的所有关键字中, 这是惟一成对存在的 。 Visual Prolog 6 把出现在
这两个关键字之间的代码看成属于一个类 。 这个类名必须在, implement”这个
关键字后给出 。
Open
这个关键字用来扩展类作用域的可见性, 它被用在 implement关键字之后 。
Constants
这个关键字用来表明一部分经常在程序中被使用的代码 。 例如, 如果字符串
,PDC Prolog”在程序中多次出现, 那么就可以定义一个它的缩写,
constants
pdc="PDC Prolog",
注意, 常量的定义以句号结束 。 与 Prolog中的变量不同, 常量应该以小写字母开
头 。
2004.11.3 AI程序设计 11
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
domains
这个关键字用来标明程序中将要用到的论域 。 这种论域声明的句法中有许多变量
的声明, 用来指示许多将来在程序中要用到的论域 。 因为本节介绍 Visual Prolog
基础性内容, 我们将不对论域的具体细节进行讨论 。
总结一下, 我们将声明那些用于论域的算符和构成算符变元的论域, 算符和复合
论域在本书的前面章节部分有详细解释 。
Class facts
这个关键字指定一个段, 这个段用来声明将在程序代码中出现的事实 。 每一个事
实由一个符号化的名字声明每个事实的变元和各个变元所属的论域 。
2004.11.3 AI程序设计 12
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
Class predicates
这一段将包含在子句部分被定义的谓词的声明 。 同样, 谓词的名称以及变元和论
域也在这一段中被声明 。
Clauses
在 Visual Prolog 6代码的所有部分中, 这一部分和传统的 Prolog 程序最为相似,
它包含对已声明谓词的定义, 我们会发现在这里使用的谓词与 class predicates
部分中声明的谓词句法相同 。
Goal
这一段定义是 Visual Prolog 6程序的主要入口点 。 更详细的解释在下面给出 。
2004.11.3 AI程序设计 13
第 二 部分:第 7章 Visual Prolog编程
7.1.2 目标 Goal
在传统的 Prolog中, 只要谓词在代码中被定义了, Prolog核心程
序就会被引导从那个谓词开始程序的执行 。 但是, 在 Visual Prolog 6
中不是这样, 作为一个编译程序, 它要生成高效率的程序执行代码 。 在
编译程序工作的时候, 代码事实上并未被执行 。 所以, 编译程序需要事
先知道程序从哪个谓词开始执行, 这样当程序被调用执行时, 它就能从
正确的地方开始 。 正如我们所期望的那样, 这个编译好的程序可以再不
需要 Visual Prolog编译程序和 VDE而独立运行 。
为了实现这些功能, 有一个专门由关键字 goal指示的段 。 把它们
作为没有自变量的特殊谓词考虑, 这种谓词就是程序开始执行的地方 。
2004.11.3 AI程序设计 14
第 二 部分:第 7章 Visual Prolog编程
7.1.3 文件考虑
通常, 将程序的所有部分放在一个文件里是很麻烦的, 这样会使程
序难于理解, 甚至有时会产生错误 。 Visual Prolog 6 使用 VDE可以
将程序代码分成不同的文件, 也可使用 VDE 将不同的代码写入不同的文
件 。 借助这种方式, 通过查找文件就可将经常用到的程序段找到 。 如果
在许多文件中都要用到一个论域, 那么可以在一个单独的文件中声明这
个论域, 然后这个文件可以被其他文件所访问 。
然而, 为了简化这个专门教程, 我们应该主要使用一个文件写这些
代码 。 在构造程序的过程中, VDE可以自动生成更多当时可以忽略的
程序, 我们将在以后的内容中学到 。
2004.11.3 AI程序设计 15
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
Visual Prolog 6将整个程序划分为不同的部分, 每一部分定义一
个类 。 在面向对象的程序语言中, 类是一组程序代码和与之相关的数
据的集合 。 这些内容在以后的叙述中将进行更多的解释 。 像前面提到
的一样, 对于不熟悉面向对象程序语言的学习者, 可以将类类似地考
虑为模块 。 通常, Visual Prolog 6在自己专门的文件中定义各个类 。
在程序执行的过程中, 程序经常需要调用在另一个类中定义的谓词 。
相似地, 在一个类中定义的数据和论域可能需要允许能被另一个不同
的文件所访问 。
2004.11.3 AI程序设计 16
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
Visual Prolog 6允许这些跨越类的代码数据引用, 称为访问作用域 。 可以
用一个例子来理解, 假设在名为 class1 的类中定义了一个名为 pred1 的谓词
( 使用 VDE 在另一个文件中写出 ), 我们在另一个文件 class2中调用另一个名
为 pred2 的谓词, 下面就是如何在 pred2的子句体中调用 pred1的例子,
pred3:-
,.,
!,
pred2:-
class1::pred1,% pred1 is not known in this file,
% It is defined in some other file,
% Hence a class qualifier is needed
pred3,
,.,
2004.11.3 AI程序设计 17
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
在上述例子中, 可以看到 pred2的子句体调用 pred1和 pred3这两个谓词, 因
为 pred1在另一文件 class1中被定义, 因此将 class1和,,:”放在 pred1的前面,
这被称为是类的限定符 。
但是谓词 pred3和 pred2一样, 在相同的文件中被定义, 因此没有必要在谓词
前加上, class2::”来调用 pred3。
这种行为在专业上这样解释,pred3的访问作用域蕴含于 pred2中, 因此没有
必要澄清 pred3和 pred2一样来自于同一个类, 编译程序会在定义 class2 的范
围内自动寻找 pred3 的定义 。
某一特定类定义的作用域范围被限制在某一特定文件中声明的类中 ( 代码写在
关键字 implement和 end implement之间 ), 在其中定义的谓词可以不用类
名限定符和,,:”符号作为前缀相互调用 。
2004.11.3 AI程序设计 18
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
类的作用域范围可以通过使用 open这个关键字予以扩充, 这个关键字可以通知
编译程序调用在其它文件中定义的谓词, 常量, 论域名 。 如果作用域范围扩充
了的话, 我们就不需要写类名限定符和,,:”。
open class1
,.,
pred3:-
,.,
!,
pred2:-
pred1,% Note,"class1::" qualifier is not needed
% anymore,as the scope area
% is extended using the 'open' keyword
pred3,
,.,
2004.11.3 AI程序设计 19
第 二 部分:第 7章 Visual Prolog编程
7.1.5 面向对象
Visual Prolog的当前版本是一个强大的面向对象语言,
如果需要的话,开发程序的整个代码会根据需要被放入合
适的类中。即使对这种语言的面向对象特性不感兴趣,它
也会自动进行。我们也会在本章给出的例子中发现这个特
性。即使我们完全不使用任何该类生成的对象,代码一样
会被插入到名为 family1的类中。我们将会直接使用这个
类中的谓词代码。
2004.11.3 AI程序设计 20
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
让我们把所学的知识集合起来去写我们的第一个 Visual Prolog 6
的程序 。 这将包含在, Prolog基础, 一章中所涉及的相同的基本逻辑 。
所有为本章所写的代码如下所示, 并写入名为 family1.pro的文件中 。
使用 Visual Prolog 的 VDE可完成代码的输入 。 本章所需要的更多文件
将由 VDE自动生成和维护 。 如何一步步使用 VDE开发程序的步骤将在
稍后做介绍 。
2004.11.3 AI程序设计 21
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
使用 VDE开发程序的步骤简要概括如下,
第一步:在 VDE中生成一个新的控制台程序 。
? 在启动 Visual Prolog 6 的 VDE之后, 在 project菜单中点击 new菜单项 。
如图 7.1所示 。
图 7.1 点击 new
菜单项
2004.11.3 AI程序设计 22
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 这时, 一个对话框将被打开 ( 参见图 7.2所示 ), 输入所有相关信息, 确定
UI Strategy 框中填写的是 Console而不是 GUI。
图 7.2 项目设
置对话框
2004.11.3 AI程序设计 23
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第二步:建立一个空的项目
? 当项目被建立时, VDE将会显示如图 7.3所示的项目窗口 。 此时, 它还不
知道这个项目依赖于哪个文件, 它只是为这个项目构造出基本的源代码文
件 。
图 7.3 项目窗口
2004.11.3 AI程序设计 24
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 使用 build菜单中的 build菜单项 ( 参见图 7.4), 编译和连接一个空的项目, 以
产生一个实际上不做任何事的可执行文件 。 ( 这时, 也不会产生任何错误 。 )
图 7.4 点击 build菜单
2004.11.3 AI程序设计 25
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 当建立一个项目时, 看看 VDE的消息窗口中显示的消息 ( 如果看不见消息窗口,
可使用 VDE 中的 windows菜单打开 ) 。 我们会发现, VDE已将所有相关的
Prolog 的基础类模型 PFC放入该项目中, PFC的类包含了建立程序所需的基
本功能类 。
2004.11.3 AI程序设计 26
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第三步:使用实际的代码组装 family1.pro
? VDE 插入的是非常基本的代码, 并不完成什么功能, 如图 7.5所示 。 可以删除
整个代码, 只需要复制和粘贴本章中给出的 family1.pro的代码到窗口中 。
图 7.5 VDE生成的基
本代码
2004.11.3 AI程序设计 27
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 当完成了复制粘贴操作之后,family1.pro的窗口将会如图 7.6所示。
图 7.6 复制和粘贴的
family1.pro代码
2004.11.3 AI程序设计 28
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第四步,rebuild代码
? 重新打开 build菜单, 若 VDE发现源代码已经改变, 它将会重新编译
改动的部分 。 如果选用 Build All这一菜单项, 所有的模型都会被重新
编译 。 对于大程序来说, 这将会耗费一定的时间 。 Build All经常只是
在一系列小的编译结束后使用, 只是为了保证每一步都按序进行 。
2004.11.3 AI程序设计 29
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 在项目建立过程中, VDE不只执行编译操作, 同样决定项目是否需要
另外的 PFC模型, 并插入必要的代码, 如果需要的话重新开始建立过
程 。 这些可在消息窗口的消息中看到 ( 参见图 7.7), 在那里会看到
由于一些附加的描述, VDE要两次建立项目 。
图 7.7 消息窗口
2004.11.3 AI程序设计 30
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第五步:执行程序
? 我们现在完成了使用 Visual Prolog 6编写的第一个应用程序 。 为了测试编
译完的应用程序, 可以使用窗口菜单命令中的 build | run命令 。 但是, 这
样会导致错误 。 原因是我们的小程序会为了函数功能去读取名为 fa.txt的文
本文件 。 这个文本文件包含程序运行所需要的数据, 我们将在以后分析这个
文本的语法 。
? 现在我们需要使用文本编辑器来编写文本文件, 并把它们放置在可执行文件
停留的路径上 。 通常, 可执行文件在主文件夹的名为 exe的子文件夹中 。
2004.11.3 AI程序设计 31
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 下面让我们使用 VDE 来生成一个这样的文本文件 。 首先使用 File | New这
一菜单项, 将弹出创建项目的选项窗口 (Create Project Item),从左边的
列表中选取文本文件 ( Text file), 再选择文件可能停留的路径 ( 可执行
文件 family1.exe 的路径 ) 。 然后给文件命名为 fa.txt,然后点击对话框
上的 Create按钮 。 直到文件名被给出, Create按钮才会失效 。 最后确保检
查框 Add to project as module被选中 。 把该文件添加到项目中去的好处
是它将使后继的调试可以进行 。
? 文件的内容如下,
2004.11.3 AI程序设计 32
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
clauses
person("Judith",female()),
person("Bill",male()),
person("John",male()),
person("Pam",female()),
parent("John","Judith"),
parent("Bill","John"),
parent("Pam","Bill"),
2004.11.3 AI程序设计 33
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 虽然这是一个数据文件, 我们会注意到这个文件的语法非常相似于
普通的 Prolog代码, 即使现在程序已经被编译结束并以二进制格式
存在, 也可以通过改变运行程序的主要数据来改变输出, 采用与一
般 Prolog相同的语法, 这些数据被写入文本文件 。 因此 Visual
Prolog 6仿效了传统 Prolog动态代码的编写能力 。 虽然并不是所有
的特性都可能, 但至少在这个例子中的复合论域可以被给出 。
? 文件 fa.txt中使用的语法必须和程序的论域定义保持一致, 定义人的
算符必须用 person来表示, 否则用来表示算符的复合论域将不能得
到初始化 ( 在前面的叙述中我们已经提到过算符和复合论域 ) 。
? 现在, 当我们使用热键 shift+F9来运行程序时 ( 或使用窗口菜单中
的 Build | Run命令 ), 将看到如图 7.8所示的内容,
2004.11.3 AI程序设计 34
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
这个程序处理文件 fa.txt中的信息,并运行出所给人物的亲戚关系的逻辑顺序。
图 7.8 消息窗口
2004.11.3 AI程序设计 35
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? 程序的逻辑非常简单, 我们已经在前面讨论过, 在那里解释了在一个 Prolog
程序中正确的数据表示如何产生适当的结果 。 让我们现在来看看逻辑工作方
式 。
? 开始的时候, main谓词将会被直接调用, 然后会转向 run谓词, run谓词所
要做的第一件事情就是读取文件 fa.txt 中的数据 。 一旦数据存在的话, 程序
就会系统地一个个地测试处理数据, 并把测试结果写在控制台上 。
? 处理数据的机制是标准的回溯和递归机制 。 在这里并不详细描述关于 Prolog
如何工作的细节, 只是简要给出了其内部工作的原理 。
? 当谓词 run调用谓词 father时, 它并不会停在 father谓词结束的地方, 在谓
词结束时 fail的调用将驱使 Prolog的推理机在 father谓词中寻找另一个可执
行的地方, 这种行为就是回溯, 因为 Prolog推理机事实上可以在执行过的程
序中回溯查找 。
?
2004.11.3 AI程序设计 36
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? 这种情形会循环发生 ( 就像重复或循环的过程 ), 并在每一次循环中谓词
father都将产生一个结果, 最后在数据中给出的所有可能的 father 的定义被
耗尽, 谓词 run别无选择只能转到谓词 run的下一个子句体 。
? 这个谓词 father( 和其他谓词一样 ) 被声明为不 确定性的, 关键字
nondeterm可以被用来声明不确定性的谓词 。 通过回溯, 不确定性谓词可
以产生多个结果 。
? 如果在谓词的声明中使用 no这个关键字, 谓词就成为仅能给出一个解答的过
程模型, 在产生第一个结果后, father谓词将会停止, 再也不能重新进入 。
而且, 在这种不确定性谓词的子句体中必须注意截断 ( ! ) 的用法 。 如果在
father谓词的子句体的末尾有截断, 并重复出现, 谓词将只产生一个结果
( 即使它被声明为不确定性的 ) 。
2004.11.3 AI程序设计 37
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? Anyflow 的流模式告诉编译程序赋予谓词的参量可以是自由的也可以是约束
的, 没有什么限制 。 这就意味着和 father谓词的定义一样, 有可能同时产生
父亲的名字 ( 如果后代的名字是约束的话 ) 和后代的名字 ( 如果父亲的名字
是约束的话 ) 。 同样可以处理两者都受约束的情况, 在这种情况下谓词会检
查这样的关系是否存在于数据中 。
? 明确地说, anyflow流模式同样适用于当所有的 father谓词里的参数是自由
变量时的情况 。 这时, father谓词将在程序学习到的数据中产生多种父子关
系的结合体 ( 通过查询 fa.txt文件 ) 。 像下面代码显示的那样, 最后的用法
是在 run谓词中所执行的内容 。 我们会注意到当 father谓词被调用时,
father谓词中的变量 x和 y并不受约束 。
2004.11.3 AI程序设计 38
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
run():-
console::init(),
stdIO::write("Load data\n"),
reconsult("fa.txt"),
stdIO::write("\nfather test\n"),
father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
2004.11.3 AI程序设计 39
第 二 部分:第 7章 Visual Prolog编程
7.1.8 小结
在这一节,我们了解到 Visual Prolog 6中的程序非常类似于传统
Prolog中的程序,有许多不同的关键字用来区分一个 Prolog 程序的不
同部分。虽然 Visual Prolog是一种面向对象的语言,但也开发不使用
面向对象特点的代码,这一节描述了基于控制台的应用程序 (family1),
并描述了如何构造这样一个程序。
我们同样发现,通过把部分代码独立于编译的二进制应用程序而保
存在数据文件中,可以模拟传统 Prolog 程序的动态工作机制,这种数
据文件的句法非常相似于 Prolog的句法。
2004.11.3 AI程序设计 40
第 二 部分:第 7章 Visual Prolog编程
7.2 Visual Prolog的 GUI编程
在这一节, 我们将增加一个简单的 GUI( 图形用户接口 ) 前端到
family项目, 这个项目是在前面一节中开发的 。 我们将学会直接在
Visual Prolog的 VDE( 可视化开发环境 ) 中创建和编辑一个简单
Windows程序的大多数 GUI组件 。 我们将学习关于 GUI事件 ( 如点击
GUI的特定部分上的一个按钮等 ), 以及它们在 Visual Prolog程序中
如何工作等有关说明 。 我们还将通过两个例子来学习关于模态对话框的
有关知识, 一个例子是由 Windows操作系统内建的, 另一个例子是我
们自己构建的程序 。
2004.11.3 AI程序设计 41
第 二 部分:第 7章 Visual Prolog编程
7.2 Visual Prolog的 GUI编程
7.2.1 GUI概述
7.2.2 GUI对事件的响应
7.2.3 开始一个 GUI项目
7.2.4 创建模态对话框
7.2.5 修改菜单
7.2.6 修改工具栏
7.2.7 在程序中添加主代码
7.2.8 压缩相关代码
7.2.9 分析所做的工作
7.2.10 运行程序
7.2.11 小结
2004.11.3 AI程序设计 42
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? GUI是图形用户接口首字母的缩写。在 Windows操作系统环境中,这个术语
表示人们熟悉的窗口,窗口带有菜单栏、工具栏、窗口右上角的小按钮等。这
些元素中的每一个元素都称作一个 GUI组件,而且在良好设计的程序中,这些
组件按照公认的规范进行工作。
? 用程序设计术语来说, 一个 GUI完成两件事 。 它使用复杂的图形例程在计算机
显示器的相应部位上安放和恢复图形图像 。 例如, 一个窗口右上角带有 x的小
方框 。 它还控制鼠标和其他输入设备在这些图形区域上的行为 。 幸运的是, 这
些详细的程序设计是由操作系统完成的 。 作为一个程序员, 并不需要编排这些
图形, 鼠标和键盘函数, Windows已经做了这一切 。 它还提供一个 API( 应用
程序编程接口 ), 可以用来建立任何程序所要求的 GUI。
? 此外, Visual Prolog已经增加了一个更高层的功能 —— PFC (Prolog基类 ),
不仅有助于使用 GUI,而且有助于其他领域的编程 。
2004.11.3 AI程序设计 43
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 更有甚者,Visual Prolog的 VDE可以用来可视化地创建正在开发的
程序所用的最终 GUI实物模型( moch-up)。我们将使用前一节中
family例子的处理逻辑。
? 在一个 GUI程序和控制台程序之间, 存在许多差异 。 然而, 他们也有
类似之处, 即控制台程序和 GUI程序总是从一个固定的入口点 ( 从
Goal或过程 ) 开始 。 控制台程序不显示图形元素, 所以程序由用户提
供输入, 这些输入或者是来自程序员预先设置的初始参数, 或者是来
自程序员预先设置的直接询问 。 程序中各种活动的顺序和方向是由程
序员确定的 。 用户不能够更改该顺序 。 在一个控制台程序中, 程序从
Goal或过程启动, 然后逻辑上从那一点开始向前工作, 严格地使用户
通过由程序员设置的一系列逻辑步 。
2004.11.3 AI程序设计 44
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 在 GUI程序中, 程序员可以做出灵活的选择 。 对于程序的哪一部分应
当如何完成, 可以给用户提供各种选项 。 例如, 如果启动任何一个常
规的 Windows程序, 一般来说它并不强迫我们做这做那 。 我们可以随
意地浏览菜单而不点击其中可选的任何一项 。 考虑这样一种特殊的情
节,File菜单有几个菜单项 。 我们可以使鼠标光标通过该菜单中的任
何一项, 而并不完成实际的菜单点选操作 。 事实上, 通过如此这般地
在一个程序的 GUI上不经意的浏览操作, 是人们达到掌控软件的常用
方法之一 。
? 无论如何,当编写 GUI程序时,程序员必须使用一种不同的策略。一
方面,编程更加容易,因为对所实现的程序中的一切活动不再硬性刻
板或严格控制。
2004.11.3 AI程序设计 45
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 但是另一方面, 程序员必须知道每一个 GUI组件是如何工作的, 什么
是常规公认的惯例, 以及如何坚持这些习惯, 以便于当使用这些程
序时, 用户不会产生不必要的惊奇 。 同样, 有时很难预测由于某些
GUI事件的触发所产生的结果 。 因此, 程序员必须仔细地分析逻辑,
以便无论 GUI如何使用都能维持逻辑的有效性, 仔细地调整这些情形,
而程序的逻辑不可以进行调整 ( 使用有礼貌的, 易于理解的警告对
话框, 等等 ) 。
2004.11.3 AI程序设计 46
第 二 部分:第 7章 Visual Prolog编程
7.2.2 GUI对事件的响应
1) 在一个 GUI程序中, 所有的 GUI组件等待来自键盘或鼠标 ( 或其他
输入设备, 如数字化仪等 ) 的输入 。
2) 来自这种输入设备的信息 ( 鼠标的点击或其他的 GUI活动 ) 称为一
个事件 ( event) 。
3) 一个事件处理器是专门等待和实际处理一个 GUI事件的一段代码 。
4) 因为一个事件处理器的代码, 只是专门等待某一事件的发生, 所以
看起来似乎毫无逻辑可言 。 其他模块甚至不必知道在实际处理一个
GUI事件的类内部秘密地发生的事情 。 在前面叙述的内容中, 我们
注意到, 包含的所有逻辑都是良好的, 它们在逻辑上完整地彼此连
接在一起 。 但是在 GUI程序的情形, 处理 GUI事件的部分程序代码
是被分成片断的, 并且被放到了不同的事件处理程序之中 。
2004.11.3 AI程序设计 47
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 当我们在 Visual Prolog 6的 VDE中创建一个项目时, 确保 UI策略设置为
GUI 。 VDE则创建最初的处理 GUI所需要的模块集合和 GUI组件资源:
主菜单, 一个顶部的工具栏, 一个底部的状态栏, About对话框和该程
序的任务窗口 ( Task Window) 。
图 7.9 family2项
目设置对话框
2004.11.3 AI程序设计 48
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 这样创建一个项目之后, 就可以立即编译它, 这是一个空的 GUI程序 。 在这
个时期, 程序实际上什么也不做 。 然而, 它拥有了一个 GUI程序所期望的全
部功能 。 Visual Prolog已经直接构造了一个运转 GUI程序的基本框架, 并
且已经提供了一些通常所需要的特性 。
? 例如, 在应用程序的主窗口 ( 称作任务窗口 Task Window), Visual
Prolog给出了另一个标题为 Message的窗口 。 这个窗口用来作为内部的控
制台程序 。 当程序员在程序中使用 stdio::write(…)谓词时, 结果将直接在
Message窗口输出 。 如果 Visual Prolog没有将 PFC类 stdio的输出重新定
向到 Message窗口, 则那些串将不会被看到, 因为按照缺省约定, 一个
GUI环境没有控制台区 。
2004.11.3 AI程序设计 49
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 在一个控制台应用程序中, 控制台总是可以作为一个黑板来使用, 程序员可
以在其上输出信息 。 这就是为什么这些应用程序被称为控制台应用程序的原
因 。 在前面的章节里可以看到, 在这样的应用程序中, 我们直接使用
stdio::write(…)谓词向控制台输出 。
? 在这一阶段运行所编译的 GUI程序时, 只可以空阅程序的菜单, 改变程序外
部主窗口 ( 即任务窗口 ) 的大小, 双击 Message窗口在任务窗口之中进行
全程缩放等等 。 Visual Prolog为 Message窗口恰好给出一个小的常常是很
方便的弹出式菜单 。 在 Message窗口内部任何地方右击鼠标, 随即就会弹
出一个小菜单, 可以清除 Message窗口的内容或进行其他的活动 。
2004.11.3 AI程序设计 50
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 现在, 我们给程序添加另一个 GUI组件, 供以后需要时使用 。 这个组件
是一个对话框, 它用来给程序提供要处理的一个人的名字 。 在项目树
( project tree) 中, 所有模块和资源都清晰地展现在树形菜单里, 右
击任务窗口, 从弹出菜单的上下文菜单中选取, New…”菜单项, 如图
7.10所示 。
图 7.10 激活创建项目条款对话框
2004.11.3 AI程序设计 51
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 创建项目条款的对话框 Create Project Item出现, 如图所示 。 确保对话
框左面的 Diaglog选项被选中, 并且在右面输入如图 7.11所示的详细信息 。
图 7.11 创建项目条
款对话框
2004.11.3 AI程序设计 52
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 这样, 一个缺省的对话框被创建了出来, 如图 7.12所示, 可供进一步编辑
加工之用 。 因为我们没有为这个对话框实现帮助功能, 所以就可以点击一
下特定的 GUI组件 ( 例如 Help按钮 ), 再按下 DEL键即可将其删除 。
图 7.12 创建的缺省对话框
2004.11.3 AI程序设计 53
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 然后,再分别选中其他两个按钮,移动到合适的位置,最后的结果变成
如图 7.13所示的那样。
图 7.13 编辑缺
省对话框
2004.11.3 AI程序设计 54
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 通过使用对话框控件, 我们给对话框插入一个静态文本 。 所谓静态 ( static)
一词代表一个 GUI元素是不可点击的 。 参考前面提到的图像, 在点击静态文
本创建按钮之后, 可以在对话框中画一个矩形区域, 所要输入的文本文字
将出现在其中, 如图 7.14所示 。
图 7.14 在缺省对话
框中增加控件
2004.11.3 AI程序设计 55
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 随之编辑控件属性
( Edit Control
Attributes)将出现,
如图 7.15所示。在
Text字段下,输入
,Person”,所输入
的文本将出现在对话
框里面。
图 7.15 编辑控件属
性对话框
2004.11.3 AI程序设计 56
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 以类似的方式, 我们使用对话框编辑控件来插入一个可编辑的文本字段
或一个编辑控件, 如图 7.16所示 。
图 7.16 编辑控件工具栏
2004.11.3 AI程序设计 57
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 此时, 我们先放下那个
空的文本字段, 转去改
变常量 ( Constant)
符号为某个有意义的符
号常量, 而不是由
VDE提供的缺省常量,
这个步骤如图 7.17所示 。
这个常量在程序代码内
部使用, 以便程序代码
引用这个编辑字段 。
图 7.17 在编辑控件属性对话框中改变常量
2004.11.3 AI程序设计 58
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 我们构造的对话框的最终的情形,如图 7.18所示
图 7.18 所创建的对话框
2004.11.3 AI程序设计 59
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 每一个对话框有一个属性称为访问次序 。 我们需要完成的最后一步, 就是设
置这个控件在对话框中被访问的次序, 即当用户按下 TAB键时, 对话框中的
GUI组件与用户交互的次序 。 短语, 访问控件 ( Visit a control), 意思是
说控件获得输入焦点, 这是一个十分专业的术语 。 短语, 控件接收输入焦点
(the control receiving the input focus)”意思是说控件立即从键盘接收
输入 。 例如, 如果一个编辑字段具有输入焦点, 就可以看到该编辑字段中闪
烁的插字符号, 表示该字段准备接收经由键盘输入的字符 。
? 为了改变访问次序, 右击该对话框, 在随即弹出的菜单中选取访问次序
( Visit Order) 菜单项 。
? 一些小按钮将出现在该对话框的 GUI组件上, 如下图 7.19所示 。 在这里可以
看到, 文本编辑控件 ( idc_ancestordialog_personname) 的访问次序
为 3,而 OK按钮的访问次序编号为 1。
2004.11.3 AI程序设计 60
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 这就是说, 当该对话框呈现
给用户的时候, 该文本编辑
控件将不会立即接收键盘的
字符 。 当用户按一下 TAB键
时, 焦点将转移到 Cancel按
钮, 只有再一次按下 TAB键
时, 焦点才转移到该文本编
辑控件 。 只有到了这个时候,
该文本编辑控件才接收键盘
的输入 。
? 简言之, 相对于该对话框中
的其他控件而言, 编辑字段
的访问次序编号是最后一个 。
图 7.19 确定对话框上控件的访问次序
2004.11.3 AI程序设计 61
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 使用同样的对话框属性 ( Dialog Attributes) ( 参见图 7.20所示 ),
我们也可以把标题改为, Ancestor of,..”。
图 7.20 对话框属性
2004.11.3 AI程序设计 62
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 现在我们将修改程序的主菜
单 。 如前所述, VDE已经
提供了一种在许多程序中都
可见到的由标准菜单项组成
的缺省菜单 。 我们要去编辑
它以满足程序的功能需求 。
通过项目树, 双击
TaskMenu.mnu项, 可以
看到如图 7.21所示的情形 。
图 7.21 双击任务菜单项
2004.11.3 AI程序设计 63
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 这将开始菜单编辑器 。 菜单编辑器的主对话框如图 7.22所示 。 确保在主目
录中选定编辑菜单 ( Edit) 后, 点击属性 ( Attributes) 按钮 。
图 7.22 菜单编辑器
2004.11.3 AI程序设计 64
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 接下来就打开了菜单项属性 Menu Item Attributes对话框, 如图 7.23所
示 。 我们可以从 &Edit到 &Query改变名字, Q前面的, &”标记指在主菜
单中该字母下面有下划线, 用户能通过该字母迅速进入菜单 。
? 我们可以点击上述对话框中的, Test”按钮来测试这个特性, VDE的顶端
菜单将会暂时被现在设计的菜单所替换,
然后我们就可以浏览,感
觉其效果。按 ESC键就可
以返回 VDE菜单的原来状
态。
图 7.23 菜单项属性对话框
2004.11.3 AI程序设计 65
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 我 们 现 在 回 到 主 任 务 菜 单
( TaskMenu) 对话框 。 双击
&Query入口, 我们会注意到
它仍然包含老的 Edit菜单里的
菜单项 (Undo,Redo,Cut,
Copy与 Paste)。 从主菜单条
目入口 &Query删除在里面看
到的所有菜单项 。 接着把常量
前缀 Constant Prefix设定为
id_query 。 常量前缀
Constant Prefix由 VDE内部使
用, 用于描述表示各种菜单项
的常量 。 这些常数被用来在代
码内引用菜单项 。
图 7.24 在任务菜单对话框设定常量前缀
2004.11.3 AI程序设计 66
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 现在我们要在 Query主菜单下增加一些菜单条目。点击 New( 如图 7.24
所示)按钮,然后输入信息,进入图 7.25所示的界面。
图 7.25 利用菜单项属性增加子菜单
2004.11.3 AI程序设计 67
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 最后一步是 TaskWindow菜单的编辑 。 在缺省状态, 当 VDE创建菜单时,
File|Open 菜 单 无 法 使 用, 必 须 找 出 该 菜 单 项, 从 Menu Item
Attributes对话框中去掉禁止标记, 使它变为可用, 如图 7.26所示 。
图 7.26 菜单项
属性对话框
2004.11.3 AI程序设计 68
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 当关闭 TaskMenu对话框时,VDE会请求确认是否想保存这个菜单。点
击 Save键保存。
图 7.27 VDE确认对话框
2004.11.3 AI程序设计 69
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 工具栏的应用是 GUI的另一个有用部分 。 通常, 它包括具有各种菜单功
能的按钮, 简单地说, 这些按钮就是这些菜单的快捷方式 。 现在我们来
编写工具栏的程序 。 当开始创建一个项目时, Visual Prolog VDE会为
程序创建一个缺省的工具栏 。 在项目树中双击 ProjectToolbar.tb,如
图 7.28所示 。
图 7.28 在项目树中双击工具栏项
2004.11.3 AI程序设计 70
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 这将调用工具栏编辑器,如图 7.29所示。
图 7.29 项目工具栏编辑器
2004.11.3 AI程序设计 71
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 在上图中, 顶端是我们正在编辑的工具栏, 底端是编辑工具栏组件可以
用到的各种控制按钮 。
? 在工具栏中, 包含一组有预定图标图形的按钮 ( 与一般 GUI程序一样 ) 。
如果我们希望, 也可以改变这些图标的图形 。 需要指出的是, Visual
Prolog的 VDE内部已经恰当地包含了一个非常好的图标编辑程序, 对大
的图标, VDE打开 MS画图工具来编辑 。
? 这些按钮已经被订制为一套功能菜单项 。 但是当我们在编辑菜单项时,
也要编辑工具栏按钮并且把它放在适当的位置 。
? 首先, 我们对剪切, 拷贝和粘贴等按钮进行删除处理, 因为程序没有这
些功能 。 为此, 首先选择这些按钮, 然后从工具栏中删除 。 删除以后,
工具栏如图 7.30所示 。
图 7.30 对话框属性
2004.11.3 AI程序设计 72
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 现在我们将把 Undo,Redo 和 Help按钮映射并表示为下列菜单项
Query|Father...,Query|Grandfather..,和 Query|Ancestor of,..。
? 我们在前面提到过, 在本章中我们将不改变那些按钮的图标图像 。
? 双击 Undo工具栏按钮, 在出现的按钮属性 Button Attributes对话框内,
将象征工具栏按钮的内部常量从 id_edit_undo 变为 id_query_father,
如图 7.31所示 。
图 7.31 按钮属性
对话框
2004.11.3 AI程序设计 73
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 在同一个对话框中, 还应该将 Status Text设置由
Undo;Undo
变为
Query fathers;List of all fathers listed in the database
? 上一行的分号将字符串分成两部分, 第一部分作为按钮本身的工具提示而显
示, 而第二部分将在主窗口的状态行显示 。
? 运用同样的方法, 改变 Redo按钮的常量值为 id_query_grandfather,改
变 Help按钮的常量值为 id_query_ancestor_of。 这两个按钮的状态行内
容也做适当的修改 。
2004.11.3 AI程序设计 74
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 我们已经完成了程序中需要的全部 GUI功能性工作 。 现在我们将开
始插入逻辑代码 。
? 在做这些工作之前, 我们来了解常用的程序运行方式和现在的 GUI
程序运行方式之间的一些重要差异 。
? 模块的内部已经包含了初级逻辑代码 。 但是现在依靠相应的 GUI组
件控制的逻辑, 它将被扩展到几个模块 。
2004.11.3 AI程序设计 75
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 让我们来增加一些程序
必需的逻辑代码。打开
TaskWindow.pro,
将插字编辑符定位在下
面的代码行,
facts
thisWin,
vpiDomains::windo
wHandle,=
erroneous,
图 7.32 任务窗口代码
2004.11.3 AI程序设计 76
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 一旦找到那一行代码且设置了插字编辑符(见图 7.32),就在那里插入下列
代码,
domains
gender = female(); male(),
class facts - familyDB
person, (string Name,gender Gender),
parent, (string Person,string Parent),
class predicates
father, (string Person,string Father) nondeterm anyflow,
clauses
father(Person,Father),-
parent(Person,Father),
person(Father,male()),
2004.11.3 AI程序设计 77
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
class predicates
grandFather, (string Person,string Grandfather) nondeterm
anyflow,
clauses
grandfather(Person,Grandfather),-
parent(Person,Parent),
father(Parent,Grandfather),
class predicates
ancestor, (string Person,string Ancestor) nondeterm
anyflow,
clauses
ancestor(Person,Ancestor),-
parent(Person,Ancestor),
2004.11.3 AI程序设计 78
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
ancestor(Person,Ancestor),-
parent(Person,P1),
ancestor(P1,Ancestor),
class predicates
reconsult, (string FileName),
clauses
reconsult(Filename),-
retractAll(_,familyDB),
file::consult(Filename,familyDB),
? 上 面 的 代 码 是 程 序 的 核 心 逻 辑 部 分 。 在 本 节, 我 们 直 接 将 他 们 插 入
TaskWindow.pro模块中, 因为我们想了解 GUI组件如何进入到程序的核心逻辑中
执行各种动作 。 在更复杂的例子中, 核心逻辑代码会分开存放, 有时分散到好几个模
块中 。 GUI模块 ( 如 TaskWindow) 通过扩大模块的范围, 分别引用这些模块 。
? 事实上, 让程序的核心逻辑代码尽可能地分散到所有 GUI相关的模块中是一个很好的
实践, 但在本节中我们将有意忽略这些 。
2004.11.3 AI程序设计 79
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 既然已经考虑了核心逻辑, 现在我们需要插入交互位 ( interactive
bits) 。 在项目树中右击 TaskWindow.win条目, 该条目表示该主任务
窗口的窗口资源 。 所有发生在这个窗口的点击, 菜单等事件都由写给这
个窗口的事件处理器处理 。 右击所选的项, 就会弹出如图 7.33所示的上
下文菜单, 从该菜单选择代码专家 Code Expert。
2004.11.3 AI程序设计 80
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
图 7.33 在右键弹
出菜单中选择
2004.11.3 AI程序设计 81
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对话框和窗口专家 (Dialog
and Window Expert)
( 如图 7.34所示 ) 帮助我
们为特定窗口或对话框控
件 ( 交互式 GUI组件 ) 设
置缺省的事件处理器 。 蓝
色填充的圆圈表示没有给
定控件的事件处理器, 绿
色的钩记号表示存在相关
的事件处理器 。
图 7.34 对话框与窗口专家
2004.11.3 AI程序设计 82
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 使用上面的对话框, 确保事件处理器是为常量 id_file_open所表示的菜单
项设置的 。
? 现在, 在项目树中点击 TaskWindow.pro模块, 选中它, 用 Build菜单来编
辑它 。 如果 TaskWindow.pro模块没有被选择, 那么 Build菜单中的
Compile菜单将不可用, 因此一定要谨慎 。
? 当用 Visual Prolog 6编辑模块时, 它要改造项目树, 使所有相关的谓词 /论
域等清晰地展开, 便于我们直接访问 。 因此我们要使用这些特点来保证
VDE列出在 TaskWindow.pro模块中定义的所有谓词 。 然后即可对我们关
心的谓词进行操作 。
2004.11.3 AI程序设计 83
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 注意, VDE自动识别出 TaskWindow.pro需要一些附加模块, 如图 7.35所
示的那样, 对该模块的编译进行了两遍 。
图 7.35 消息窗口
2004.11.3 AI程序设计 84
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 编译完成以后,当我们引用 TaskWindow谓词时,会在项目树中看到所
有的谓词,如图 7.36所示。
图 7.36 项目树中的谓词
2004.11.3 AI程序设计 85
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 我们会发现 taskwindow.pro模块的谓词部分将显示出谓词 onFileOpen,
而原来它是不存在的 。
? 双击该谓词, 就会直接打开编辑器, 并定位于谓词 onFileOpen的子句位置
处 。 当该谓词有多重子句时, 指针将指向第一个子句体 。
? 谓词 onFileOpen被称作事件处理器 ( windows应用程序术语 ), 作为程序
员, 不需要访问这个谓词 。 对应的 GUI组件被激活时, windows会自动调
用它 。 在这里单击该菜单项, 缺省情况下为事件处理器插入了如下代码,
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 86
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 用下面的代码替换该代码如下,
onFileOpen(_MenuTag) = handled(0),-
Filename = vpiCommonDialogs::getFileName(
"*.txt",["Family data files (*.txt)","*.txt","All files",
"*.*"],
"Load family database",
[],".",_),
!,
reconsult(Filename),
stdIO::writef("Database % loaded\n",Filename),
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 87
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对于对话框和窗口专
家 ( Dialog and
Window Expert),
确保 Query |
Father,Query |
Grandfather和
Query | Ancestor
of,..菜单项的事件
处理程序设置如图
7.37所示。
图 7.37 设置菜单项
的事件处理程序
2004.11.3 AI程序设计 88
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对于 Query|Father菜单项, 在 Visual Prolog 自动生成的子句体前添加如
下子句体,
onQueryFather(_MenuTag) = handled(0),-
stdIO::write("\nfather test\n"),
father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
? 对于 Query|grandFather菜单项, 在 Visual Prolog自动生成的子句体前
添加如下代码,
onQueryGrandFather(_MenuTag) = handled(0),-
stdIO::write("\ngrandFather test\n"),
grandfather(X,Y),
stdIO::writef("% is the grandfather of %\n",Y,X),
fail,
2004.11.3 AI程序设计 89
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 现在, 也许我们会问, 哪里是封装? 其实, 我们已将代码分成两部分, 前面
所讲到的是非交互式的逻辑核心 。 而这部分需要接收用户的输入, 并且这些
部分被存储为不同的事件处理器, 至于其他的程序, 它并不需要知道这些事
件处理器在程序中怎样编写, 下面就让我们添加一些交互代码到其他事件处
理器中 。
? 在 Visual Prolog自动产生的缺省子句体前, 为 Query | Ancestor of...菜
单项增加如下子句体,
onQueryAncestorOf(_MenuTag) = handled(0),-
X = ancestorDialog::getName(thisWin),
stdIO::writef("\nancestor of % test\n",X),
ancestor(X,Y),
stdIO::writef("% is the ancestor of %\n",Y,X),
fail,
2004.11.3 AI程序设计 90
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 上面代码的目的就是从前面已经构造的 ancestorDialog所提供的模态对
话框中获取一个字符串 。 代码的谓词部分假设有一个全局可达的叫做
getName的谓词, 该谓词在 ancestorDialog模块中可用, 它将会返回某
人的名字, 这个人的祖先正是我们要寻找的对象 。
? 与在 onFileOpen 事件处理器中看到的一样, 我们寻找一个从模态对话框
返回的字符串 ( 文件名 ) 。 模 态 对 话 框 本 身 被 谓 词
vpiCommonDialogs::getFileName(...) 调用 。 它与我们从
ancestorDialog获得字符串的过程采用了相同的策略 。 惟一不同的是:
vpiCommonDialogs::getFileName(...) 提供了一个内置的标准的窗口
模式对话框文件, 但是对于自己家族的 ancestorDialog,我们不得不做
更多的编码工作 。
2004.11.3 AI程序设计 91
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 编译这个程序时, 可能会出现一个错误, 如图 7.38所示 。
? 错误的原因是因为谓词 onQueryAncestorOf所期盼的全局可达谓词
gerName从 ancestorDialog.pro模块进行调用 。 但是还没那样写, 下面
让我们来纠正这些错误 。
图 7.38 编译错误信息
2004.11.3 AI程序设计 92
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 这个谓词在一个模块中定义, 但是在其他模块中调用, 因此需要我们确保这种
声明不保存在,pro文件中, 但是放在一个所有程序都能调用的位置 。 模块的
类声明文件 (extension.cl) 就是这样的位置 。 所以, 让我们打开
ancestorDialog.cm,添加下面的代码 ( 这个声明表示 getName谓词在模块
内执行, 也能被其他模块调用 ) 。
predicates
getName, (vpiDomains::windowHandle Parent) -> string
Name determ,
? 在模块 ancestorDialog.pro 里, 添加该模块的相关核心逻辑 。 与
Taskwindow.pro设计的方式一样, 就是将这些代码插入下面的代码行前,
facts
thisWin, vpiDomains::windowHandle,= erroneous,
? 下面就是要插入的代码,
2004.11.3 AI程序设计 93
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
domains
optionalString = none(); one(string Value),
class facts
name, optionalString,= none(),
clauses
getName(Parent) = Name,-
name,= none(),
Dlg = ancestorDialog::new(),
Dlg:show(Parent),
one(Name) = name,
? 现在还有最后一个问题 。 我们需要改变 OK按钮的事件处理器, 这是必须的,
以便对话框把由用户输入的值声明进 name事实类 。 不做这些, 上面的谓词
将会发现一个空的字符串 。
Visual Prolog给出的缺省代码如下,
2004.11.3 AI程序设计 94
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
predicates
onControlOK, vpiDomains::controlHandler,
Clauses
onControlOK(_Ctrl,_CtrlType,_CtrlWin,_CtrlInfo) = handled(0),-
vpi::winDestroy(thisWin),
上面的代码将被改写如下,
predicates
onControlOK, vpiDomains::controlHandler,
clauses
onControlOK(_Ctrl,_CtrlType,_CtrlWin,_CtrlInfo) = handled(0),-
EditCtrl = vpi::winGetCtlHandle(thisWin,
idc_ancestordialog_personname),
Name = vpi::winGetText(EditCtrl),
name,= one(Name),
vpi::winDestroy(thisWin),
? 现在我们已经完成了程序,如果现在编译并运行这个程序,将不会有任何错误。
2004.11.3 AI程序设计 95
第 二 部分:第 7章 Visual Prolog编程
7.2.10 运行程序
? 一旦开始执行程序, 要注意到不会立即执行这些活动 。 在前面, 我们已经介
绍了控制台程序的工作方式 。 像前面说的一样, 开始一个 GUI程序就像要进
入一个房间, 在那里用户可以自由地完成他选择的任何事情 。 每个房间仅仅
是等待用户决定给出的输入 。
? 类似地, 在这小程序中, 我们能不装载任何数据调用 Query|Query
Father菜单 。 它不会产生任何结果或错误, 因为我们的主要逻辑只关心缺
省数据的情况, 我们可以在选项中调用 File|Open…菜单, 并装入 family数
据库 ( 前面的教程中用了同样的一部分 ) 。
? 之后, 我们可以测试 Query | Query Father,Query|Query Ancestor
和 Query | Ancestor of...来查看控制台程序获得的上述结果 。 这些结果
将被显示在消息窗口 ( 注意不要关闭消息窗口, 否则结果不会被显示出来 ) 。
当我们发现可以多趟运行询问时, GUI程序将被认可, 我们可在任何时间点
上自由地调用不同数据 。
2004.11.3 AI程序设计 96
第 二 部分:第 7章 Visual Prolog编程
7.2.11 小结
? 在这一节, 我们已经研究了 GUI的基本元素以及如何用 Visual
Prolog开发一个 GUI程序 。 我们发现 Visual Prolog给出的 VDE允
许我们完全控制我们希望使用的所有 GUI组件 。 我们可以编辑菜单,
对话框和工具栏, 以满足所希望的功能 。
? 我们可以模块化 Prolog代码, 并且将代码插入程序的不同部分, 以
便他们被安全地封装到不同的事件处理器中 。 非交互式逻辑核心被
分别保存, 因此开发的 GUI程序可以被灵活使用, 也不强迫用户关
心程序执行活动的顺序 。
返回
2004.11.3 AI程序设计 97
第 二 部分:第 7章 Visual Prolog编程
7.3 Visual Prolog的逻辑层
在这一节, 我们将修改在上一节开发的简单 GUI( 图形用户接
口 ) 程序, 并将程序的逻辑从其余的 GUI代码中隔离出来 。 这一
代码逻辑会被放入一个属于自己的程序包的对象内 。 而隔离代码
的这一个方法会使我们清楚地从 GUI中去思考 ( 代码 ) 逻辑问题 。
2004.11.3 AI程序设计 98
第 二 部分:第 7章 Visual Prolog编程
7.3 Visual Prolog的逻辑层
7.3.1 初始准备阶段
7.3.2 创建业务逻辑层
7.3.3 在业务逻辑层上工作
7.3.4 创建业务逻辑类
7.3.5 理解业务逻辑类
7.3.6 连接业务逻辑层到 GUI
7.3.7 理解事件处理程序
7.3.8 运行代码
7.3.9 细节考虑
7.3.10 小结
2004.11.3 AI程序设计 99
第 二 部分:第 7章 Visual Prolog编程
7.3.1 初始准备阶段
? 在这一节中, 我们需要以正确的步骤安装项目的 GUI,完全像我们在上一节
中所说明的步骤那样 。 我们假定, 读者已经正确完成了那些 GUI构造过程,
因此这里不再重复 。
? 注意:我们假定读者已经在 GUI中创建了 AncestorDialog对话框, 并且已
给定了代码, 因此该模块已经定义了谓词 getName来检索在该对话框输入
的祖先的名字 。
? 我们假设该项目名字为 family3。
? 程序的 GUI在技术上著称为表示层 。 较早的时候, 逻辑代码被混合在表示层
内 。 现在, 我们将取出该逻辑并把它放入项目内自己的程序包中 。 此程序包
可用作业务逻辑层 (business logic layer)。
2004.11.3 AI程序设计 100
第 二 部分:第 7章 Visual Prolog编程
7.3.1 初始准备阶段
? 注意, 我们在使用句子, business logic” 而不是, logic”,因为它指示程
序的, business” 层面 。 简言之, 程序存在的理由来自于逻辑层 。
? 因为 Visual Prolog 6是一种强有力的面向对象语言, 业务逻辑会被表示为
一个对象 。 关于深入地理解 Visual Prolog 6面向对象编程的细节, 读者可
参见, 类与对象, 一章中的详细解释 。 但为了完整起见,有关面向对象的一
些思想在本节中也会提到 。 而在此之前, 让我们逐步完成程序 。
2004.11.3 AI程序设计 101
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 一旦安装好 family3项目的 GUI,就会看到如图 7.39所示的主项目目录。
图 7.39 family3的项目树
2004.11.3 AI程序设计 102
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 右击目录的顶端,从弹出的菜单中点击 New菜单项,如图 7.40所示。
图 7.40 在 family3项目树中的右键菜单
2004.11.3 AI程序设计 103
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 从出现的创建项目条款 Create Project Item对话框, 如图 7.41所示,
选择 Package (左边列表的第一项 ),并且给定 package的命名为
familyBLL( 为使家庭业务逻辑层容易记忆, 应该选择有意义的名字 ) 。
图 7.41 在创建项目对话
框键入程序包的名字
2004.11.3 AI程序设计 104
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 单击创建 Create按钮,项目目录呈现出的内容如图 7.42所示。
如果现在查看硬盘上存储这些文件的的文件夹, 那么就会看到, VDE已经
在主项目文件夹内创建了一个单独的文件夹 ( 叫做 familyBLL) 存储某一程序
包的所有文件 。
现在, 建立程序, 以确保工作有序完成 。
图 7.42 在 family3项目
树中的新内容
2004.11.3 AI程序设计 105
第 二 部分:第 7章 Visual Prolog编程
7.3.3 在业务逻辑层上工作
? 让我们在适当的位置放置逻辑 。 我们将充分利用 Visual Prolog 6面向对象
的特性产生且设计 familyBLL程序包的一个 familyBL类 。 在某些叙述中,
有些特征并没有说明 。 然而, 这里我们将使用面向对象系统的主要特征之一,
即数据封装 。
? 数据封装这一术语指将数据隐藏在某一特定模块的代码内, 程序的其他模块
不能直接读取或修改其中的数据 。 数据存取被严格控制, 并且数据只能间接
地通过该类的谓词读取或修改 。
? 这一原理避免了许多程序设计语言中最普遍犯的程序设计错误 。 当数据被保
存下来为项目的所有程序设计者所读写时, 程序设计时常无法快速写出访问
代码和改变此类的数据 。 这是非常成问题的, 尤其是在非同步的程序中, 比
如 GUI程序, 数据在哪里改变无法预知;时常依照人的行为使用程序 。
? 数据封装的另外一个优点是内在的数据表达系统能随时在类内被修正, 项目
内其余的代码不会受到影响 。
2004.11.3 AI程序设计 106
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 在项目目录中的 familyBLL程序包文件夹上右击 。 从打开的菜单上面
点击 New…,会看到如图 7.43所示的情形 。
图 7.43 在 family3项目
树中的新内容
2004.11.3 AI程序设计 107
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 从出现的创建项目条
款 Create Project
Item对话框, 创建
一个名为 familyBL
的类, 确保 Creates
Object选项框选中,
可以看到如图 7.44所
示的情形 。
图 7.44 为项目创建 familyBL类
2004.11.3 AI程序设计 108
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 单击 Create按钮, VDE将会给出 familyBL.pro文件让编辑 。 替换从 implement到
end implement间的缺省代码为下列代码,
implement familyBL
open core
constants
className = "FamilyBLL/familyBL",
classVersion = "$JustDate,$$Revision,$",
clauses
classInfo(className,classVersion),
facts
fileName, string,
clauses
new(Filename),-
filename,= Filename,
file::consult(Filename,familyDB),
2004.11.3 AI程序设计 109
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
clauses
save(),-
file::save(filename,familyDB),
facts - familyDB
person, (string Name,gender Gender),
parent, (string Person,string Parent),
clauses
father(Person,Father),-
parent(Person,Father),
person(Father,male()),
clauses
grandfather(Person,Grandfather),-
parent(Person,Parent),
father(Parent,Grandfather),
clauses
ancestor(Person,Ancestor),-
parent(Person,Ancestor),
ancestor(Person,Ancestor),-
parent(Person,P1),
ancestor(P1,Ancestor),
end implement
2004.11.3 AI程序设计 110
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 现在, 点选 VDE的 File | Open的菜单项, 打开文件 familyBL.i,用下面的
代码替换看到的缺省代码,
interface familyBL
open core
domains
gender = female(); male(),
predicates
father, (string Person,string Father) nondeterm (o,o) (i,o),
predicates
grandFather, (string Person,string Grandfather) nondeterm
(o,o),
predicates
ancestor, (string Person,string Ancestor) nondeterm (i,o),
predicates
save,(),
end interface
2004.11.3 AI程序设计 111
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 由 VDE自动生成的很多文件需要被修正 。 点选 VDE的 File|Open菜单项,并
且 打开 familyBL.cl文件 。
? 在文件中见到的谓词段之前, 需要插入以下代码,
constructors
new, (string Filename),
? 在插入上述代码之后, 文件会是下面这样,
class familyBL, familyBL
open core
constructors
new, (string Filename),
predicates
classInfo, core,:classInfo,
% @shot Class information predicate,
% @detail This predicate represents inform
% @end
end class familyBL
2004.11.3 AI程序设计 112
第 二 部分:第 7章 Visual Prolog编程
7.3.5 理解业务逻辑类
? 如果比较我们现在写的代码与早期教程中的代码,可能会得到这样的结论,
1)代码被分成三个段。 2)一些谓语和论域被声明在,i文件中。
3)所有的实现被放在,pro文件中。 4),cl文件包含特殊谓语 constructor的
声明。
? 当我们要求 VDE建立一个类的时候,我们指出类会产生对象 (objects)。 区别
一个类和一个对象之间的不同点是很重要的。
? 尽管类是在项目中被编码,但它并不意味着项目自动包含类的对象。在每个
被设计的用于产生对象的类中,那里肯定会有一个或多个特殊的构造函数
constructor,它将构建一个包含所有相关代码和数据的预先封装好的对象。
2004.11.3 AI程序设计 113
第 二 部分:第 7章 Visual Prolog编程
7.3.5 理解业务逻辑类
? 类的构造函数写在,cl文件中。可以自由存取整个类的谓词 (与类产生的对象
相反 ) 也存在于该文件中。这可以在 classInfo实用谓词中看到,它也是在该
文件中声明。
? 创建的每个对象需要有一些可公共存取的谓词操纵封装在它里面的数据。这
样的谓词被分别定义在相关类的,i 文件中。
? 最后,执行文件 (.pro扩展 )将包含实际代码。对于所有的实际用途,代码在
其他模块中是不可见的。
2004.11.3 AI程序设计 114
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在项目树中, 点击 TaskWindow.win项, 选择代码专家 Code Expert,
如图 7.45所示 。 (当 TaskWindow.win在项目目录中被选中的时候,
也可以使用 Ctrl-W 热键 )。
图 7.45 在项目树中点击 TaskWindow.win
2004.11.3 AI程序设计 115
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在代码专家 ( 对话
框与窗口专家 ) 对
话框中, 确保
id_file_open,
id_Query_father,
id_Query_grand
_father 和
id_Query_ancest
or_of 菜单项的缺
省处理程序已经设
置, 就像图 7.46所
示的那样 。
图 7.46 设置菜单的缺省处理程序
2004.11.3 AI程序设计 116
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 正确退出应用程序的 id_file_exit事件处理器已经被 VDE设置, 因此,
不需要再一次设定它 。
? 在我们将正确的代码移植到处理器之前, 显示层应该包含一个事实,
该事实用来存储某时刻点程序处理的对象 。 因此, 在
TaskWindow.pro文件中, 必须给出下列代码,
facts
familyBL_db, (familyBL BL) determ,
2004.11.3 AI程序设计 117
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 上述事实将包含表示 family的业务逻辑层的对象。
? 对于 id_file_open菜单项,将会给出以下事件处理器代码,
onFileOpen(_MenuTag) = handled(0),-
Filename = vpiCommonDialogs::getFileName(
"*.txt",
["Family data files (*.txt)","*.txt",
"All files","*.*"],
"Load family database",
[],".",_),
!,
BL = familyBL::new(Filename),
retractAll(familyBL_db(_)),
assert(familyBL_db(BL)),
stdIO::writef("Database % loaded\n",Filename),
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 118
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在我们写另一个事件处理器之前,需要插入一个实用谓词如下所示,
predicates
tryGetFamilyBL:()->familyBL BL determ,
clauses
tryGetFamilyBL() = BL,-
familyBL_db(BL),
!,
tryGetFamilyBL() = _,-
stdIO::write("No family database loaded\n"),
fail,
2004.11.3 AI程序设计 119
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 对于 id_Query_Father菜单项, 将给出以下事件处理器代码,
onQueryFather(_MenuTag) = handled(0),-
stdIO::write("\nfather test\n"),
BL = tryGetFamilyBL(),
BL:father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
onQueryFather(_MenuTag) = handled(0),
? 对于 id_Query_Grand_Father菜单项目, 将给出以下事件处理器代码,
onQueryGrandFather(_MenuTag) = handled(0),-
stdIO::write("\ngrandFather test\n"),
BL = tryGetFamilyBL(),
BL:grandfather(X,Y),
stdIO::writef("% is the grandfather of %\n",Y,X),
fail,
onQueryGrandFather(_MenuTag) = handled(0),
2004.11.3 AI程序设计 120
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 最后, 对于 id_Query_Ancestor,将给出以下事件处理器代码,
onQueryAncestorOf(_MenuTag) = handled(0),-
X = ancestorDialog::getName(thisWin),
stdIO::writef("\nancestor of % test\n",X),
BL = tryGetFamilyBL(),
BL:ancestor(X,Y),
stdIO::writef("% is the ancestor of %\n",Y,X),
fail,
onQueryAncestorOf(_MenuTag) = handled(0),
? 观察上面的代码, 我们会注意到三个事件处理器都使用 tryGetFamilyBL()
实用谓词, 试图获得需要的对象, 而不是直接访问事实段 。 在这种情况, 数
据库不被装载, 实用谓词会在消息窗口给出一个适当的消息后失败 。
2004.11.3 AI程序设计 121
第 二 部分:第 7章 Visual Prolog编程
7.3.7 理解事件处理程序
? 如果分析我们现在开发的事件处理程序,并且把它与那些在 GUI中开发的处
理程序作比较,就会注意到他们只有很小的不同。
? 最重要的是在第一个事件处理器 (onFileOpen) 中。程序构造了一个新的
程序事务层类的对象,并且将对象存入该模块的事实 facts段。
? 要注意的另外一个关键点是业务层对象公共存取方法的使用,这些对象已经
存储在 TaskWindow.pro模块的 facts段中。
? 例如,在下列代码片断中,程序获得一个业务逻辑层对象,并赋给变量 BL,
然后运行该对象的公共存取谓词 grandfather。
? 变量和谓词之间的冒号算符,,”是一个逻辑指示器。获得对象公共存取谓词
的冒号算符,,”和获得整个类的公共存取谓词的双冒号算符,,:” 不同。这
可在上面的代码片断 stdIO::writef(...)的谓词调用中看到。需要注意,双
冒号算符不能用在对象上,而冒号算符不能用在类上。
2004.11.3 AI程序设计 122
第 二 部分:第 7章 Visual Prolog编程
7.3.8 运行代码
? 现在,我们能够编译并且运行该程序。如果有错误,那么应该重新
阅读代码,或者也可以查看以前的内容去检查程序中的错误。需要
注意的一点是:当设置事件处理器的时候,不要修改以前已经设定好
的事件处理器的名字。
? 当运行这一程序时,我们会发现程序运行方式和以前的一样。这是
面向对象程序设计的一个好的基础:它允许相当多的内部编码发生
变化但最终的运行功能并不发生变化。
2004.11.3 AI程序设计 123
第 二 部分:第 7章 Visual Prolog编程
7.3.9 细节考虑
? 在面向对象的程序设计系统中, 类事实变成对象事实, 因为每一个对象将
封装它自己的事实 。
? 当我们使用对象表达数据时, 数据的维护变得更加容易 。 如果我们不是用
面向对象的方法, 我们可能需要用 retract谓词 ( retract是 Visual Prolog
6的一个内部谓词 ) 清除剩余的早期数据 。 然而, 现在 retract不再需要,
因为一个新对象建立的同时, 旧的对象会自动被程序通过一个垃圾回收过
程予以清除 。 这个操作在已知的安全条件下, 在后台自动发生, 不会影响
用户 。
? 声明谓词时, 谓词的参数流模式必须要明确地添加到谓词声明中, 就像我
们在,i文件中所做的 。 anyflow流模式不能被用在全局实体上 。
? 在事务层的执行代码中, 我们执行了一个保存数据到一个文件的谓词 。 注
意, 这个保存数据的谓词并不需要文件名, 这是因为文件名存储在对象中 。
下面作为一个练习, 读者可以将这个功能与一些合适的 GUI事件处理器结
合起来 。
2004.11.3 AI程序设计 124
第 二 部分:第 7章 Visual Prolog编程
7.3.10 小结
在本节我们学习到, 业务逻辑及其表示层 ( GUI) 的数据
的较细分离不仅是可能的而且是强烈推荐的 。 Visual Prolog 6
能够智能化地帮助我们创建一个包括所有类的程序包, 这些类用
于管理这些分离的事务 。 在这个特殊教程中, 一个类被开发在一
个程序包中, 这种类可以创建封装数据和业务逻辑的对象 。 其他
模块可以使用这些对象运行与业务逻辑层有关的一些内容 。 这样,
系统维护和软件模块的开发变得更加容易, 减少了出错的可能性 。
2004.11.3 AI程序设计 125
第 二 部分:第 7章 Visual Prolog编程
7.4 Visual Prolog的数据层
在本节, 我们将继续以 family为例来介绍 Visual Prolog能够
更好地处理的下一个抽象层次 —— 数据层 。 本节给出了处理复杂
情况所需的各种组合 。
这里提供的范例项目源文件, 只能运行在 Visual Prolog的
商业版 (Commercial Edition) 环境之下 。
2004.11.3 AI程序设计 126
第 二 部分:第 7章 Visual Prolog编程
7.4 Visual Prolog的数据层
7.4.1 基本概念
7.4.2 程序
7.4.3 非模态对话框
7.4.4 FamilyData包
7.4.5 接口
7.4.6 FamilyDL包
7.4.7 FamilyDL包的代码
7.4.8 FamilyBLL包的代码
7.4.9 数据层的特征
7.4.10 小结
2004.11.3 AI程序设计 127
第 二 部分:第 7章 Visual Prolog编程
7.4.1 基本概念
? 尽管有人说引用的层次越多, 就越容易控制最终结果的实现, 但是
在设计层次以前还是需要仔细的考虑 。
? 如果看到整个 family系列 (Visual Prolog基本层, GUI层, 逻辑层 )
的展开模式, 就会发现:随着引用越来越多的精确的层, 程序的运
行将得到越来越细微的控制 。 同时, 不要有多余的层 。 否则, 它只
能增加程序的复杂性和麻烦性 。
? 在本节, 我们将介绍数据层 ( Data Layer) 。 数据层的核心就是一
个类, 以及使用该类来访问和设置实际数据的对象 。 在上一节, 业
务逻辑层 ( Business Logical Layer,BLL) 也可以处理数据 。 而
在这里, 业务逻辑层 BLL只处理逻辑 。
2004.11.3 AI程序设计 128
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 当在 Visual Prolog的 VDE中安装项目 family4时,就会发现四个独立
的数据包,如图 7.47所示。
图 7.47 family4的项目树
2004.11.3 AI程序设计 129
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 与前面章节中所给出的例子相比, TaskWindow 程序包增加了一个
新的对话框, 即 NewPersonDialog,如图 7.48所示 。 项目中已经包
含了这个对话框, 下面的解释假设要在项目中手动生成该对话框 。
图 7.48 TaskWindow
新增对话框
2004.11.3 AI程序设计 130
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 模态与非模态对话框在创建上没有区别。只是在属性设置上要清楚地指明对
话框的类型,如图 7.49所示。
图 7.49 设置对
话框类型
2004.11.3 AI程序设计 131
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 光创建这样一个对话框是没用的,
TaskWindow必须能够调用它。
这样我们就需要插入一个新的菜
单项目和与之相关的事件处理程
序。 一旦我们创建了一个适当
的菜单项目,代码专家将允许我
们创建一个处理程序来添加必要
的代码,如图 7.50所示。
图 7.50 设置事件处理程序
2004.11.3 AI程序设计 132
第 二 部分:第 7章 Visual Prolog编程
7.4.3 非模态对话框
? 非模态对话框是一个概念问题 。 这个程序通过使用一个非模态对话
框来收集 family数据库中新增人员的信息 。 前面曾指出, 对应于由
非模态对话框引起的事件发生的代码有时具有欺骗性 。 如果我们了
解模态对话框的工作方式就不难理解这一点了 。 在模态对话框中,
操作系统暂停由同一应用程序中所有其他的 GUI部分所引起的事件
发生, 只将注意力放在由模态对话框所引起的事件发生上 。 在时间
逻辑上程序员需要处理的内容就变得很简单了, 因为程序员很清楚,
与此同时不会有同一程序其他部分复杂事件的发生 。
2004.11.3 AI程序设计 133
第 二 部分:第 7章 Visual Prolog编程
7.4.3 非模态对话框
? 另一方面, 当使用一个非模态对话框时, 程序不仅可以容纳由程序其他部分
引起的事件发生, 也可以容纳由对话框引起的事件发生 。 这样程序员就需要
谨慎地考虑事件处理的排列 ( permutations) 与组合 ( combinations) 。
例如, 程序打开一个数据库, 然后再打开一个该数据库下的一个非模态对话
框 。 这样当数据库关闭而对话框没有关闭时, 对话框的使用就有可能产生错
误 。 当然, 除非程序员已经预料到事件发生的过程并针对这种情况进行设计 。
? 在 NewPersonDialog.pro中, 如果看到了用于处理由对话框引起的事件发
生的源代码, 就会发现该代码捕捉到很多错误发生的情形 。 它甚至有一个完
整性检查器用于跟踪可能导致一段代码产生错误的原因 。
2004.11.3 AI程序设计 134
第 二 部分:第 7章 Visual Prolog编程
7.4.4 FamilyData包
? 在这个例子当中,我们将创建一个 FamilyData包,它包含了用于处理程序
所需论域的所有必需代码。当业务逻辑层与数据层通信时,相同的论域可以
访问 BLL (Business Logic Layer)。
在这个类中,我们编写如下代码,
class familyData
open core
domains
gender = female(); male(),
domains
person = string,
person_list = person*,
domains
optionalPerson = noPerson(); person(person Person),
2004.11.3 AI程序设计 135
第 二 部分:第 7章 Visual Prolog编程
7.4.4 FamilyData包
predicates
toGender, (string GenderString)
-> gender Gender,
predicates
toGenderString, (gender Gender)
-> string GenderString,
predicates
classInfo, core::classInfo,
end class familyData
? 注意, 上面 的类 中包 含了 对多 个类 可共享 的论 域的 定义 。 因此,
FamilyData包担当了数据层和业务层之间会议厅的作用 。
2004.11.3 AI程序设计 136
第 二 部分:第 7章 Visual Prolog编程
7.4.5 接口
? 用来处理数据的层需要经过由 FamilyDL包中的一个类所产生的对象 。 然而,
在获得该包的实际内容以前, 我们首先需要了解接口的概念 。
? 接口可以看作是对与之相应对象的期望意图的描述 。 通过 VDE将它写在一
个单独的文件中, 并包含了能够在类对象中可以找到的谓词 。
? 接口也可以看作是对本节中定义的进一步解释 。 读者将会在后面的内容学到
有关这些接口的高级用法 。
? 将目的相对独立的内容写在一个不同的接口中, 原因很充分:根据一个接口
的声明, 可以写出一个类 。 接口只对类对象所提供的功能作一个简要的定义 。
之所以说简要定义是因为它仅仅对对象的某些功能进行了表达 。 简而言之,
类可以公开声称其对象行为与接口一致 。
? 不同的类可执行同一个接口;每一个类提供一个专门的, 具体的 ( 即非抽象
的 ) 执行 。 这大大减轻了代码维护的负担 。 稍后, 当同一程序中出现更多的
组合情况时, 程序员可以系统地在这些接口上工作 。
2004.11.3 AI程序设计 137
第 二 部分:第 7章 Visual Prolog编程
7.4.6 FamilyDL包
? 这个例子程序还包含了另外一个程序包, 即 FamilyDL包 。 该程序包包含了
主类, 其对象用于处理实际的数据 。
? 该程序包包含一个接口, 即 familydl接口 。 该接口的主要用途是定义由数据
层提供的面向业务逻辑层的谓词 。 同样它在数据层中也起了很重要的作用 。
该接口建立在 FamilyData包中论域的基础上 。
interface familyDL
open core,familyData
predicates
person_nd, (person Name) nondeterm (o),
predicates
gender, (person Name) -> gender Gender,
2004.11.3 AI程序设计 138
第 二 部分:第 7章 Visual Prolog编程
7.4.6 FamilyDL包
predicates
parent_nd, (
person Person,
person Parent)
nondeterm (i,o),
predicates
addPerson, (person Person,gender Gender),
predicates
addParent, (person Person,person Parent),
predicates
personExists, (person Person) determ,
predicates
save, (),
end interface familyDL
2004.11.3 AI程序设计 139
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
? familyDL包有一个接口文件 familydl.i,它的代码在以前的章节中给出过 。 该包中也
有 两 个,pro 文件, 其 中 包 含 了 两 个,cl 类 文 件 的 实 现 。 这 两 个 类 为,
familydl_exception 和 familydl_factfile。 本节中没有 familydl_factfile的代码,
而该代码可以通过检查下载文件 family4.zip来得到 。 该文件中的谓词用于处理数据,
这一点在前面已经提到过 。
? 本节中也没有 familydl_exception 的代码, 而该代码可以在文件 family4.zip中看
到 。 这些谓词包括了支持实用程序的谓词:当程序发现不正确数据或错误 ( 也就是电
脑术语里所谓的异常 ) 时, 实用程序谓词用于显示相应的错误信息 。 很多错误情形的
发生是超出程序员控制范围的 ( 例如, 驱动门被打开就是一个无法预料的异常 ), 但
是有时候需要, 制造, 一个错误 。 也就是说, 程序员能够故意引发 ( raise) 一个错
误, ( 这里用的是计算机界的行话 ) 。 这时, 我们会发现, 在代码中错误实用程序谓
词以术语前缀 raise_开头 。
? 值得注意的是 familydl_exception自身不包括处理异常的逻辑 。 当异常发生时, 应
用程序谓词允许程序显示纠正错误对话框 。
2004.11.3 AI程序设计 140
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
? 下面的例子说明了程序员如何通过故意调用这些 raise_谓词来告知一个错误的情形 。
? 在 familydl_factfile.pro中, 能够找到用于添加父母双亲的谓词 。 在子句当中, 首先
运行两个检查操作, 查看 Person与 Person的双亲 Parent是否已经存在, 然后再调
用 familyDL_exception::raise_personAlreadyExists。 这样就构造了一个异常,
程序会出示一个针对这种情况的一个专门的错误对话框 。 幸运的是, 这个专门的错误
对话框不需要程序员构建, 而是由 Visual Prolog的库在程序连接时生成的 。 程序员
不必像生成其他对话框那样, 专门生成一个错误对话框 。
% note,the following is only partial
% code from familydl_factfile.pro
clauses
addParent(Person,Parent),-
check_personExists(Person),
check_personExists(Parent),
assertz(parent_fct(Person,Parent)),
2004.11.3 AI程序设计 141
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
clauses
personExists(Person),-
person_fct(Person,_),
!,
predicates
check_personDoesNotExist, (string Person),
clauses
check_personDoesNotExist(Person),-
personExists(Person),
!,
familyDL_exception::raise_personAlreadyExists(
classInfo,Person),
check_personDoesNotExist(_),
2004.11.3 AI程序设计 142
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
? 下面给出 familyBL.i接口文件的代码 。 这与前面章节提到的业务层的接口十
分相似 。 但是, 它们有十分重要的区别:论域已经移动到程序包之外, 在数
据层和业务层之间共享 。 这是一个很显然的移动, 因为在先前的介绍中, 我
们把数据层和业务层当作同一段代码 。 当我们将这两层分开时, 我们仍然需
要某一公共端口, 通过该公共端口, 这两层的代码可以互相对话, 显然我们
需要将这两层的代码使用的论域变成为两者的公共端口 。
? 第二个注意到的变化是谓词不会有多个流程 。 一个谓词只能对它控制的参数
准确地做一件事情 。 这使得程序偏离了传统的 Prolog,变得与其它语言编
写的程序很相似 。 通常, 这个策略产生出更好的结果 。 因为它使人们读源代
码的时候更容易理解程序 。 但是, 先前有多个流程的方法确实会产生更短的
源代码, 而且经常成为那些用过传统 Prolog编程人员的首选方法 。
2004.11.3 AI程序设计 143
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
interface familyBL
open core,familyData
predicates
personWithFather_nd, (
person Person,
person Father)
nondeterm (o,o),
predicates
personWithGrandfather_nd, (
person Person,
person GrandFather)
nondeterm (o,o),
predicates
ancestor_nd, (
person Person,
person Ancestor)
2004.11.3 AI程序设计 144
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
nondeterm (i,o),
predicates
addPerson, (
person Person,
gender Gender,
optionalPerson Parent1,
optionalPerson Parent2),
predicates
save, (),
end interface familyBL
? 这里没有给出 familyBL类的源代码 。 一旦将所提供例子的代码装载到 VDE
中, 读者就会读到这些代码 。 这里没有什么新奇的东西 。 它同在先前所介绍
的业务逻辑层中执行的行为是一样的 。 但是, 有两点重要的不同:一旦需要
访问数据或者数据设置, 都会用到 familyDL类中的谓词 。 同时, 检查这个
类的谓词信息的正确性, 当发现逻辑错误的时候, 导致异常产生 。
2004.11.3 AI程序设计 145
第 二 部分:第 7章 Visual Prolog编程
7.4.9 数据层的特征
? 数据层的主要特点是它在面向对象语言环境中实现了一组称之为数据访问器
的谓词 。 数据访问器把数据访问同底层执行分离开来 。 简言之, 可以使用谓
词设置或获得程序所使用的数据 。 实际的数据会保持私有, 导致外部无法访
问 。
? 抛开隐藏属性的具体结构, 数据访问器同样能提供统一的接口来访问数据的
属性 。
? 使用数据访问器的优点在于:在程序的其它部分所实现的算法能独立于数据
层中原来所用的数据的有关知识 。 在以后的内容中, 将会看到在保持原样的
同时, 数据能容易地移入到一个更健壮的系统中, 譬如通过 ODBC层数据访
问来使用微软的 Access数据库 。
2004.11.3 AI程序设计 146
第 二 部分:第 7章 Visual Prolog编程
7.4.9 数据层的特征
? 这种方法几乎不存在任何缺陷 。 即使偶尔存在缺陷, 它们都能被正确理解 。
可能存在的主要缺陷是, 当编写数据层的时候, 程序员在理解上需要达到两
个复杂的层次 。 一个是确定内部数据结构在数据层中保持私有 ( 这种需要开
发数据结构的概念在前面已经讨论过 ) 。 但是在此之后, 程序员仍将必须花
费时间设计数据访问器谓词, 这样其它的层同数据层能平稳地匹配起来 。 当
一组程序员一起工作的时候, 数据访问器谓词不正确的设计通常能产生许多
令人头疼的事情, 尤其是当程序从一个简单的部分移入到一个复杂的部分中
时 。 这些问题都将在以后的内容中看到 。
2004.11.3 AI程序设计 147
第 二 部分:第 7章 Visual Prolog编程
7.4.10 小结
在这一节的程序中, 我们使用了这样一个策略,, 授之以渔,
而不是授之以鱼, 。 这只是将程序的源代码巧妙地模块化, 这样
代码维护与扩展变得特别容易 。 我们把上一节中的业务层分成两
部分:一部分包含纯粹的业务逻辑, 另一部分仅仅是处理数据 。
这要求我们随后去扩充程序, 仅用其他形式的数据处理技术来代
换数据层 ( 例如通过一个 ODBC协议, 或者通过因特网等重新获得
数据 ) 。 这一节同样涵盖了异常处理, 我们能够很容易地编写自
己的异常处理谓词, 然后通过它们向用户显示有用的错误信息 。
2004.11.3 AI程序设计 148
第 二 部分:第 7章 Visual Prolog编程
本章小结
? 本章介绍 Visual Prolog编程方面的知识, 主要内容包括
Visual Prolog编程基础知识, Visual Prolog的 GUI编
程, Visual Prolog的逻辑层编程, Visual Prolog的数
据层编程等 。
? 本章包含了 Visual Prolog编程知识和工程应用程序开发
的十分重要的内容 。
2004.11.3 AI程序设计 149
第 二 部分:第 7章 Visual Prolog编程
习 题
1,传统的 Prolog与 Visual Prolog 6之间的差别主要体现在哪些方面?
2,从结构上讲, Visual Prolog的程序主要包括哪些段? 各段分别有什么功能?
3,按照书中介绍的步骤, 测试完整的例子程序 family1.prj6。
4,GUI程序与控制台程序有何异同?
5,什么叫模态对话框和非模态对话框?
6,根据 7.2节的内容, 学习如何用 Visual Prolog 6开发并运行一个 GUI程序 。 学会菜
单项的设置与相应事件处理程序的结合方法 。 最后实践 7.2节给出的例子
( family2.zip) 。
7,什么叫业务逻辑层? 业务逻辑层及其表示层 ( GUI) 数据的较细分离有什么意义?
8,上机实践 7.3节中将业务逻辑与表示层分离的方法 。 ( family3.zip)
9,将数据处理从业务逻辑层分离的目的是什么? 业务逻辑层与数据层有何关系? 在
Visual Prolog的 VDE中安装项目 family4,实践并研究增加数据层后程序结构, 组
成上等发生的变化 。
第 二 部分:第 7章 Visual Prolog编程
第 7章 Visual Prolog编程
本章介绍基于 Visual Prolog编程方面的知识,
主 要 内 容 包 括 Visual Prolog 基础, Visual
Prolog的 GUI编程, Visual Prolog的逻辑层,
Visual Prolog的数据层 。
2004.11.3 AI程序设计 2
第 二 部分:第 7章 Visual Prolog编程
第 7章 Visual Prolog编程
7.1 Visual Prolog基础
7.2 Visual Prolog的 GUI编程
7.3 Visual Prolog的逻辑层
7.4 Visual Prolog的数据层
本章小结与习题
2004.11.3 AI程序设计 3
第 二 部分:第 7章 Visual Prolog编程
7.1 Visual Prolog基础
传统的 Prolog与 Visual Prolog 6之间的差别主要体现在如下几个方面,
1)程序结构
很明显, 传统 Prolog中所使用的结构与 Visual Prolog 6中使用的结构, 在理
解的难易程度方面不同 。 主要包括如何规划来自定义 ( definitions) 的声明
( declarations), 以及如何简要地说明程序必须使用指定关键字
( keywords) 进行查找的主目标 Goal。
2)文件考虑
Visual Prolog 6提供了各种工具, 以便将程序结构组织成不同类型的文件 。
3)作用域访问
Visual Prolog 6可以挑选在其他模块中通过使用称为作用域标识 ( Scope
identification) 的概念而开发出来的功能 。
4)面向对象
Visual Prolog 6程序可以编写成面向对象的程序, 使用标准的面向对象特性 。
2004.11.3 AI程序设计 4
第 二 部分:第 7章 Visual Prolog编程
7.1 Visual Prolog基础
7.1.1 程序结构
7.1.2 目标 Goal
7.1.3 文件考虑
7.1.4 作用域访问
7.1.5 面向对象
7.1.6 一个完整的例子,family1.prj6
7.1.7 程序的取舍
7.1.8 小结
2004.11.3 AI程序设计 5
第 二 部分:第 7章 Visual Prolog编程
7.1.1 程序结构
Visual Prolog的程序, 从结构上讲, 主要包括若干个段, 即论
域段, 谓词段, 子句段, 目标段等 。 Visual Prolog作为强类型的
编译型语言, 通常用论域段和谓词段来给出有关的声明或定义 。
2004.11.3 AI程序设计 6
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
声明 (Declaration)与定义 (Definition)有着不同的含义 。
在 Prolog中, 当需要使用一个谓词的时候, 就可以直接使用, 无需事
先向 Prolog 推理机做任何的通告 。 例如, 在 前 面 的 例 子 中,
grandFather谓词的子句就是利用传统的 Prolog的谓词头和谓词体结构
直接写下来的 。 我们不用在代码中再告知推理机这个谓词结构是后面需
要使用的 。
类似地, 当在传统的 Prolog中使用一个复合论域时, 无须首先告诫
Prolog推理机关于使用该论域有何意图 。 只要感到需要, 就可以直接使
用一个论域 。
然而, 在 Visual Prolog 6中, 在编写一个谓词的子句体代码之前,
我们必须首先对编译器声明这样一个谓词的存在 。 类似地, 在使用任何
论域之前, 也必须先进行声明, 以便将这些论域的存在告知编译器 。
2004.11.3 AI程序设计 7
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
在 Visual Prolog 6中需要这种预先告知功能的原因本质上是为了保
证将运行时间异常 ( running exceptions) 尽可能地转变为编译时间错
误 ( compile time errors) 。
对于, 运行时间异常,, 我们指的是只在运行所编译的程序期间出现
的问题 。 例如, 如果我们想使用一个整数作为一个函数的参数, 但是却
错误地使用了实数, 这就会成为一个运行错误 ( 这大都出现在使用其他
编译器的程序中, 但不是在 Visual Prolog 6中 ), 程序就会因此而失败 。
当声明已定义的谓词和论域时, 这类位置语法, 即哪一个参变量属于
哪一个论域, 就会对编译器起作用 。 因此, 当 Visual Prolog 6 执行编
译时, 它将比较彻底的检查程序, 以发现诸如此类的语法错误及其它错
误 。
2004.11.3 AI程序设计 8
第 二 部分:第 7章 Visual Prolog编程
7.1.1.1 声明与定义
由于 Visual Prolog 6 的这些特性, 整个程序的效率因此提高了 。
程序员不必等到程序实际执行时才发现错误 。 事实上, 对于实际编写
程序的人, 将体会到这大大地节约了时间 。 通常, 运行时导致发生运
行时间异常的条件如此难以捉摸, 以致于错误可能会在很多年后才被
发现, 或者会在许多特别重要的情况或令人尴尬的场合表现出来 。
所有这些表明, 编码中存在的论域和谓词要在定义前给出合适的声
明, 以给编译器详尽的指示 。
2004.11.3 AI程序设计 9
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
一个 Visual Prolog 6 的程序包括一组被标点分为不同部分的代码,
由特定的关键字告诉编译程序所要生成的类型 。 例如, 关键字可以将谓
词和论域的定义和声明区分开 。 通常, 每一部分由一关键字开始, 在每
一部分结束时, 一般没有关键字指示 。 新的关键字的出现表明前一部分
的结束和下一部分的开始 。
对这一规则的例外是关键字, implement”和, end implement”,
在这两个关键字中间的代码表示它们属于一个特殊的类 。 若有人不懂类
的概念, 可以把它看作程序的一个模块或一个部分 。
在本节中, 我们将只介绍下述关键字 。 同样我们给出了这些关键字的
用途, 具体的句法可以从文档资料中学到 。 Visual Prolog 6 中还有其它
一些关键字, 可以在以后的内容和文档资料中学到 。
本章需要掌握的关键字在下面分别描述 。
2004.11.3 AI程序设计 10
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
Implement和 end implement
在这里讨论的所有关键字中, 这是惟一成对存在的 。 Visual Prolog 6 把出现在
这两个关键字之间的代码看成属于一个类 。 这个类名必须在, implement”这个
关键字后给出 。
Open
这个关键字用来扩展类作用域的可见性, 它被用在 implement关键字之后 。
Constants
这个关键字用来表明一部分经常在程序中被使用的代码 。 例如, 如果字符串
,PDC Prolog”在程序中多次出现, 那么就可以定义一个它的缩写,
constants
pdc="PDC Prolog",
注意, 常量的定义以句号结束 。 与 Prolog中的变量不同, 常量应该以小写字母开
头 。
2004.11.3 AI程序设计 11
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
domains
这个关键字用来标明程序中将要用到的论域 。 这种论域声明的句法中有许多变量
的声明, 用来指示许多将来在程序中要用到的论域 。 因为本节介绍 Visual Prolog
基础性内容, 我们将不对论域的具体细节进行讨论 。
总结一下, 我们将声明那些用于论域的算符和构成算符变元的论域, 算符和复合
论域在本书的前面章节部分有详细解释 。
Class facts
这个关键字指定一个段, 这个段用来声明将在程序代码中出现的事实 。 每一个事
实由一个符号化的名字声明每个事实的变元和各个变元所属的论域 。
2004.11.3 AI程序设计 12
第 二 部分:第 7章 Visual Prolog编程
7.1.1.2 关键字
Class predicates
这一段将包含在子句部分被定义的谓词的声明 。 同样, 谓词的名称以及变元和论
域也在这一段中被声明 。
Clauses
在 Visual Prolog 6代码的所有部分中, 这一部分和传统的 Prolog 程序最为相似,
它包含对已声明谓词的定义, 我们会发现在这里使用的谓词与 class predicates
部分中声明的谓词句法相同 。
Goal
这一段定义是 Visual Prolog 6程序的主要入口点 。 更详细的解释在下面给出 。
2004.11.3 AI程序设计 13
第 二 部分:第 7章 Visual Prolog编程
7.1.2 目标 Goal
在传统的 Prolog中, 只要谓词在代码中被定义了, Prolog核心程
序就会被引导从那个谓词开始程序的执行 。 但是, 在 Visual Prolog 6
中不是这样, 作为一个编译程序, 它要生成高效率的程序执行代码 。 在
编译程序工作的时候, 代码事实上并未被执行 。 所以, 编译程序需要事
先知道程序从哪个谓词开始执行, 这样当程序被调用执行时, 它就能从
正确的地方开始 。 正如我们所期望的那样, 这个编译好的程序可以再不
需要 Visual Prolog编译程序和 VDE而独立运行 。
为了实现这些功能, 有一个专门由关键字 goal指示的段 。 把它们
作为没有自变量的特殊谓词考虑, 这种谓词就是程序开始执行的地方 。
2004.11.3 AI程序设计 14
第 二 部分:第 7章 Visual Prolog编程
7.1.3 文件考虑
通常, 将程序的所有部分放在一个文件里是很麻烦的, 这样会使程
序难于理解, 甚至有时会产生错误 。 Visual Prolog 6 使用 VDE可以
将程序代码分成不同的文件, 也可使用 VDE 将不同的代码写入不同的文
件 。 借助这种方式, 通过查找文件就可将经常用到的程序段找到 。 如果
在许多文件中都要用到一个论域, 那么可以在一个单独的文件中声明这
个论域, 然后这个文件可以被其他文件所访问 。
然而, 为了简化这个专门教程, 我们应该主要使用一个文件写这些
代码 。 在构造程序的过程中, VDE可以自动生成更多当时可以忽略的
程序, 我们将在以后的内容中学到 。
2004.11.3 AI程序设计 15
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
Visual Prolog 6将整个程序划分为不同的部分, 每一部分定义一
个类 。 在面向对象的程序语言中, 类是一组程序代码和与之相关的数
据的集合 。 这些内容在以后的叙述中将进行更多的解释 。 像前面提到
的一样, 对于不熟悉面向对象程序语言的学习者, 可以将类类似地考
虑为模块 。 通常, Visual Prolog 6在自己专门的文件中定义各个类 。
在程序执行的过程中, 程序经常需要调用在另一个类中定义的谓词 。
相似地, 在一个类中定义的数据和论域可能需要允许能被另一个不同
的文件所访问 。
2004.11.3 AI程序设计 16
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
Visual Prolog 6允许这些跨越类的代码数据引用, 称为访问作用域 。 可以
用一个例子来理解, 假设在名为 class1 的类中定义了一个名为 pred1 的谓词
( 使用 VDE 在另一个文件中写出 ), 我们在另一个文件 class2中调用另一个名
为 pred2 的谓词, 下面就是如何在 pred2的子句体中调用 pred1的例子,
pred3:-
,.,
!,
pred2:-
class1::pred1,% pred1 is not known in this file,
% It is defined in some other file,
% Hence a class qualifier is needed
pred3,
,.,
2004.11.3 AI程序设计 17
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
在上述例子中, 可以看到 pred2的子句体调用 pred1和 pred3这两个谓词, 因
为 pred1在另一文件 class1中被定义, 因此将 class1和,,:”放在 pred1的前面,
这被称为是类的限定符 。
但是谓词 pred3和 pred2一样, 在相同的文件中被定义, 因此没有必要在谓词
前加上, class2::”来调用 pred3。
这种行为在专业上这样解释,pred3的访问作用域蕴含于 pred2中, 因此没有
必要澄清 pred3和 pred2一样来自于同一个类, 编译程序会在定义 class2 的范
围内自动寻找 pred3 的定义 。
某一特定类定义的作用域范围被限制在某一特定文件中声明的类中 ( 代码写在
关键字 implement和 end implement之间 ), 在其中定义的谓词可以不用类
名限定符和,,:”符号作为前缀相互调用 。
2004.11.3 AI程序设计 18
第 二 部分:第 7章 Visual Prolog编程
7.1.4 作用域访问
类的作用域范围可以通过使用 open这个关键字予以扩充, 这个关键字可以通知
编译程序调用在其它文件中定义的谓词, 常量, 论域名 。 如果作用域范围扩充
了的话, 我们就不需要写类名限定符和,,:”。
open class1
,.,
pred3:-
,.,
!,
pred2:-
pred1,% Note,"class1::" qualifier is not needed
% anymore,as the scope area
% is extended using the 'open' keyword
pred3,
,.,
2004.11.3 AI程序设计 19
第 二 部分:第 7章 Visual Prolog编程
7.1.5 面向对象
Visual Prolog的当前版本是一个强大的面向对象语言,
如果需要的话,开发程序的整个代码会根据需要被放入合
适的类中。即使对这种语言的面向对象特性不感兴趣,它
也会自动进行。我们也会在本章给出的例子中发现这个特
性。即使我们完全不使用任何该类生成的对象,代码一样
会被插入到名为 family1的类中。我们将会直接使用这个
类中的谓词代码。
2004.11.3 AI程序设计 20
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
让我们把所学的知识集合起来去写我们的第一个 Visual Prolog 6
的程序 。 这将包含在, Prolog基础, 一章中所涉及的相同的基本逻辑 。
所有为本章所写的代码如下所示, 并写入名为 family1.pro的文件中 。
使用 Visual Prolog 的 VDE可完成代码的输入 。 本章所需要的更多文件
将由 VDE自动生成和维护 。 如何一步步使用 VDE开发程序的步骤将在
稍后做介绍 。
2004.11.3 AI程序设计 21
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
使用 VDE开发程序的步骤简要概括如下,
第一步:在 VDE中生成一个新的控制台程序 。
? 在启动 Visual Prolog 6 的 VDE之后, 在 project菜单中点击 new菜单项 。
如图 7.1所示 。
图 7.1 点击 new
菜单项
2004.11.3 AI程序设计 22
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 这时, 一个对话框将被打开 ( 参见图 7.2所示 ), 输入所有相关信息, 确定
UI Strategy 框中填写的是 Console而不是 GUI。
图 7.2 项目设
置对话框
2004.11.3 AI程序设计 23
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第二步:建立一个空的项目
? 当项目被建立时, VDE将会显示如图 7.3所示的项目窗口 。 此时, 它还不
知道这个项目依赖于哪个文件, 它只是为这个项目构造出基本的源代码文
件 。
图 7.3 项目窗口
2004.11.3 AI程序设计 24
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 使用 build菜单中的 build菜单项 ( 参见图 7.4), 编译和连接一个空的项目, 以
产生一个实际上不做任何事的可执行文件 。 ( 这时, 也不会产生任何错误 。 )
图 7.4 点击 build菜单
2004.11.3 AI程序设计 25
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 当建立一个项目时, 看看 VDE的消息窗口中显示的消息 ( 如果看不见消息窗口,
可使用 VDE 中的 windows菜单打开 ) 。 我们会发现, VDE已将所有相关的
Prolog 的基础类模型 PFC放入该项目中, PFC的类包含了建立程序所需的基
本功能类 。
2004.11.3 AI程序设计 26
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第三步:使用实际的代码组装 family1.pro
? VDE 插入的是非常基本的代码, 并不完成什么功能, 如图 7.5所示 。 可以删除
整个代码, 只需要复制和粘贴本章中给出的 family1.pro的代码到窗口中 。
图 7.5 VDE生成的基
本代码
2004.11.3 AI程序设计 27
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 当完成了复制粘贴操作之后,family1.pro的窗口将会如图 7.6所示。
图 7.6 复制和粘贴的
family1.pro代码
2004.11.3 AI程序设计 28
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第四步,rebuild代码
? 重新打开 build菜单, 若 VDE发现源代码已经改变, 它将会重新编译
改动的部分 。 如果选用 Build All这一菜单项, 所有的模型都会被重新
编译 。 对于大程序来说, 这将会耗费一定的时间 。 Build All经常只是
在一系列小的编译结束后使用, 只是为了保证每一步都按序进行 。
2004.11.3 AI程序设计 29
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 在项目建立过程中, VDE不只执行编译操作, 同样决定项目是否需要
另外的 PFC模型, 并插入必要的代码, 如果需要的话重新开始建立过
程 。 这些可在消息窗口的消息中看到 ( 参见图 7.7), 在那里会看到
由于一些附加的描述, VDE要两次建立项目 。
图 7.7 消息窗口
2004.11.3 AI程序设计 30
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
第五步:执行程序
? 我们现在完成了使用 Visual Prolog 6编写的第一个应用程序 。 为了测试编
译完的应用程序, 可以使用窗口菜单命令中的 build | run命令 。 但是, 这
样会导致错误 。 原因是我们的小程序会为了函数功能去读取名为 fa.txt的文
本文件 。 这个文本文件包含程序运行所需要的数据, 我们将在以后分析这个
文本的语法 。
? 现在我们需要使用文本编辑器来编写文本文件, 并把它们放置在可执行文件
停留的路径上 。 通常, 可执行文件在主文件夹的名为 exe的子文件夹中 。
2004.11.3 AI程序设计 31
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 下面让我们使用 VDE 来生成一个这样的文本文件 。 首先使用 File | New这
一菜单项, 将弹出创建项目的选项窗口 (Create Project Item),从左边的
列表中选取文本文件 ( Text file), 再选择文件可能停留的路径 ( 可执行
文件 family1.exe 的路径 ) 。 然后给文件命名为 fa.txt,然后点击对话框
上的 Create按钮 。 直到文件名被给出, Create按钮才会失效 。 最后确保检
查框 Add to project as module被选中 。 把该文件添加到项目中去的好处
是它将使后继的调试可以进行 。
? 文件的内容如下,
2004.11.3 AI程序设计 32
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
clauses
person("Judith",female()),
person("Bill",male()),
person("John",male()),
person("Pam",female()),
parent("John","Judith"),
parent("Bill","John"),
parent("Pam","Bill"),
2004.11.3 AI程序设计 33
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
? 虽然这是一个数据文件, 我们会注意到这个文件的语法非常相似于
普通的 Prolog代码, 即使现在程序已经被编译结束并以二进制格式
存在, 也可以通过改变运行程序的主要数据来改变输出, 采用与一
般 Prolog相同的语法, 这些数据被写入文本文件 。 因此 Visual
Prolog 6仿效了传统 Prolog动态代码的编写能力 。 虽然并不是所有
的特性都可能, 但至少在这个例子中的复合论域可以被给出 。
? 文件 fa.txt中使用的语法必须和程序的论域定义保持一致, 定义人的
算符必须用 person来表示, 否则用来表示算符的复合论域将不能得
到初始化 ( 在前面的叙述中我们已经提到过算符和复合论域 ) 。
? 现在, 当我们使用热键 shift+F9来运行程序时 ( 或使用窗口菜单中
的 Build | Run命令 ), 将看到如图 7.8所示的内容,
2004.11.3 AI程序设计 34
第 二 部分:第 7章 Visual Prolog编程
7.1.6 一个完整的例子,family1.prj6
这个程序处理文件 fa.txt中的信息,并运行出所给人物的亲戚关系的逻辑顺序。
图 7.8 消息窗口
2004.11.3 AI程序设计 35
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? 程序的逻辑非常简单, 我们已经在前面讨论过, 在那里解释了在一个 Prolog
程序中正确的数据表示如何产生适当的结果 。 让我们现在来看看逻辑工作方
式 。
? 开始的时候, main谓词将会被直接调用, 然后会转向 run谓词, run谓词所
要做的第一件事情就是读取文件 fa.txt 中的数据 。 一旦数据存在的话, 程序
就会系统地一个个地测试处理数据, 并把测试结果写在控制台上 。
? 处理数据的机制是标准的回溯和递归机制 。 在这里并不详细描述关于 Prolog
如何工作的细节, 只是简要给出了其内部工作的原理 。
? 当谓词 run调用谓词 father时, 它并不会停在 father谓词结束的地方, 在谓
词结束时 fail的调用将驱使 Prolog的推理机在 father谓词中寻找另一个可执
行的地方, 这种行为就是回溯, 因为 Prolog推理机事实上可以在执行过的程
序中回溯查找 。
?
2004.11.3 AI程序设计 36
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? 这种情形会循环发生 ( 就像重复或循环的过程 ), 并在每一次循环中谓词
father都将产生一个结果, 最后在数据中给出的所有可能的 father 的定义被
耗尽, 谓词 run别无选择只能转到谓词 run的下一个子句体 。
? 这个谓词 father( 和其他谓词一样 ) 被声明为不 确定性的, 关键字
nondeterm可以被用来声明不确定性的谓词 。 通过回溯, 不确定性谓词可
以产生多个结果 。
? 如果在谓词的声明中使用 no这个关键字, 谓词就成为仅能给出一个解答的过
程模型, 在产生第一个结果后, father谓词将会停止, 再也不能重新进入 。
而且, 在这种不确定性谓词的子句体中必须注意截断 ( ! ) 的用法 。 如果在
father谓词的子句体的末尾有截断, 并重复出现, 谓词将只产生一个结果
( 即使它被声明为不确定性的 ) 。
2004.11.3 AI程序设计 37
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
? Anyflow 的流模式告诉编译程序赋予谓词的参量可以是自由的也可以是约束
的, 没有什么限制 。 这就意味着和 father谓词的定义一样, 有可能同时产生
父亲的名字 ( 如果后代的名字是约束的话 ) 和后代的名字 ( 如果父亲的名字
是约束的话 ) 。 同样可以处理两者都受约束的情况, 在这种情况下谓词会检
查这样的关系是否存在于数据中 。
? 明确地说, anyflow流模式同样适用于当所有的 father谓词里的参数是自由
变量时的情况 。 这时, father谓词将在程序学习到的数据中产生多种父子关
系的结合体 ( 通过查询 fa.txt文件 ) 。 像下面代码显示的那样, 最后的用法
是在 run谓词中所执行的内容 。 我们会注意到当 father谓词被调用时,
father谓词中的变量 x和 y并不受约束 。
2004.11.3 AI程序设计 38
第 二 部分:第 7章 Visual Prolog编程
7.1.7 程序的取舍
run():-
console::init(),
stdIO::write("Load data\n"),
reconsult("fa.txt"),
stdIO::write("\nfather test\n"),
father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
2004.11.3 AI程序设计 39
第 二 部分:第 7章 Visual Prolog编程
7.1.8 小结
在这一节,我们了解到 Visual Prolog 6中的程序非常类似于传统
Prolog中的程序,有许多不同的关键字用来区分一个 Prolog 程序的不
同部分。虽然 Visual Prolog是一种面向对象的语言,但也开发不使用
面向对象特点的代码,这一节描述了基于控制台的应用程序 (family1),
并描述了如何构造这样一个程序。
我们同样发现,通过把部分代码独立于编译的二进制应用程序而保
存在数据文件中,可以模拟传统 Prolog 程序的动态工作机制,这种数
据文件的句法非常相似于 Prolog的句法。
2004.11.3 AI程序设计 40
第 二 部分:第 7章 Visual Prolog编程
7.2 Visual Prolog的 GUI编程
在这一节, 我们将增加一个简单的 GUI( 图形用户接口 ) 前端到
family项目, 这个项目是在前面一节中开发的 。 我们将学会直接在
Visual Prolog的 VDE( 可视化开发环境 ) 中创建和编辑一个简单
Windows程序的大多数 GUI组件 。 我们将学习关于 GUI事件 ( 如点击
GUI的特定部分上的一个按钮等 ), 以及它们在 Visual Prolog程序中
如何工作等有关说明 。 我们还将通过两个例子来学习关于模态对话框的
有关知识, 一个例子是由 Windows操作系统内建的, 另一个例子是我
们自己构建的程序 。
2004.11.3 AI程序设计 41
第 二 部分:第 7章 Visual Prolog编程
7.2 Visual Prolog的 GUI编程
7.2.1 GUI概述
7.2.2 GUI对事件的响应
7.2.3 开始一个 GUI项目
7.2.4 创建模态对话框
7.2.5 修改菜单
7.2.6 修改工具栏
7.2.7 在程序中添加主代码
7.2.8 压缩相关代码
7.2.9 分析所做的工作
7.2.10 运行程序
7.2.11 小结
2004.11.3 AI程序设计 42
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? GUI是图形用户接口首字母的缩写。在 Windows操作系统环境中,这个术语
表示人们熟悉的窗口,窗口带有菜单栏、工具栏、窗口右上角的小按钮等。这
些元素中的每一个元素都称作一个 GUI组件,而且在良好设计的程序中,这些
组件按照公认的规范进行工作。
? 用程序设计术语来说, 一个 GUI完成两件事 。 它使用复杂的图形例程在计算机
显示器的相应部位上安放和恢复图形图像 。 例如, 一个窗口右上角带有 x的小
方框 。 它还控制鼠标和其他输入设备在这些图形区域上的行为 。 幸运的是, 这
些详细的程序设计是由操作系统完成的 。 作为一个程序员, 并不需要编排这些
图形, 鼠标和键盘函数, Windows已经做了这一切 。 它还提供一个 API( 应用
程序编程接口 ), 可以用来建立任何程序所要求的 GUI。
? 此外, Visual Prolog已经增加了一个更高层的功能 —— PFC (Prolog基类 ),
不仅有助于使用 GUI,而且有助于其他领域的编程 。
2004.11.3 AI程序设计 43
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 更有甚者,Visual Prolog的 VDE可以用来可视化地创建正在开发的
程序所用的最终 GUI实物模型( moch-up)。我们将使用前一节中
family例子的处理逻辑。
? 在一个 GUI程序和控制台程序之间, 存在许多差异 。 然而, 他们也有
类似之处, 即控制台程序和 GUI程序总是从一个固定的入口点 ( 从
Goal或过程 ) 开始 。 控制台程序不显示图形元素, 所以程序由用户提
供输入, 这些输入或者是来自程序员预先设置的初始参数, 或者是来
自程序员预先设置的直接询问 。 程序中各种活动的顺序和方向是由程
序员确定的 。 用户不能够更改该顺序 。 在一个控制台程序中, 程序从
Goal或过程启动, 然后逻辑上从那一点开始向前工作, 严格地使用户
通过由程序员设置的一系列逻辑步 。
2004.11.3 AI程序设计 44
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 在 GUI程序中, 程序员可以做出灵活的选择 。 对于程序的哪一部分应
当如何完成, 可以给用户提供各种选项 。 例如, 如果启动任何一个常
规的 Windows程序, 一般来说它并不强迫我们做这做那 。 我们可以随
意地浏览菜单而不点击其中可选的任何一项 。 考虑这样一种特殊的情
节,File菜单有几个菜单项 。 我们可以使鼠标光标通过该菜单中的任
何一项, 而并不完成实际的菜单点选操作 。 事实上, 通过如此这般地
在一个程序的 GUI上不经意的浏览操作, 是人们达到掌控软件的常用
方法之一 。
? 无论如何,当编写 GUI程序时,程序员必须使用一种不同的策略。一
方面,编程更加容易,因为对所实现的程序中的一切活动不再硬性刻
板或严格控制。
2004.11.3 AI程序设计 45
第 二 部分:第 7章 Visual Prolog编程
7.2.1 GUI概述
? 但是另一方面, 程序员必须知道每一个 GUI组件是如何工作的, 什么
是常规公认的惯例, 以及如何坚持这些习惯, 以便于当使用这些程
序时, 用户不会产生不必要的惊奇 。 同样, 有时很难预测由于某些
GUI事件的触发所产生的结果 。 因此, 程序员必须仔细地分析逻辑,
以便无论 GUI如何使用都能维持逻辑的有效性, 仔细地调整这些情形,
而程序的逻辑不可以进行调整 ( 使用有礼貌的, 易于理解的警告对
话框, 等等 ) 。
2004.11.3 AI程序设计 46
第 二 部分:第 7章 Visual Prolog编程
7.2.2 GUI对事件的响应
1) 在一个 GUI程序中, 所有的 GUI组件等待来自键盘或鼠标 ( 或其他
输入设备, 如数字化仪等 ) 的输入 。
2) 来自这种输入设备的信息 ( 鼠标的点击或其他的 GUI活动 ) 称为一
个事件 ( event) 。
3) 一个事件处理器是专门等待和实际处理一个 GUI事件的一段代码 。
4) 因为一个事件处理器的代码, 只是专门等待某一事件的发生, 所以
看起来似乎毫无逻辑可言 。 其他模块甚至不必知道在实际处理一个
GUI事件的类内部秘密地发生的事情 。 在前面叙述的内容中, 我们
注意到, 包含的所有逻辑都是良好的, 它们在逻辑上完整地彼此连
接在一起 。 但是在 GUI程序的情形, 处理 GUI事件的部分程序代码
是被分成片断的, 并且被放到了不同的事件处理程序之中 。
2004.11.3 AI程序设计 47
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 当我们在 Visual Prolog 6的 VDE中创建一个项目时, 确保 UI策略设置为
GUI 。 VDE则创建最初的处理 GUI所需要的模块集合和 GUI组件资源:
主菜单, 一个顶部的工具栏, 一个底部的状态栏, About对话框和该程
序的任务窗口 ( Task Window) 。
图 7.9 family2项
目设置对话框
2004.11.3 AI程序设计 48
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 这样创建一个项目之后, 就可以立即编译它, 这是一个空的 GUI程序 。 在这
个时期, 程序实际上什么也不做 。 然而, 它拥有了一个 GUI程序所期望的全
部功能 。 Visual Prolog已经直接构造了一个运转 GUI程序的基本框架, 并
且已经提供了一些通常所需要的特性 。
? 例如, 在应用程序的主窗口 ( 称作任务窗口 Task Window), Visual
Prolog给出了另一个标题为 Message的窗口 。 这个窗口用来作为内部的控
制台程序 。 当程序员在程序中使用 stdio::write(…)谓词时, 结果将直接在
Message窗口输出 。 如果 Visual Prolog没有将 PFC类 stdio的输出重新定
向到 Message窗口, 则那些串将不会被看到, 因为按照缺省约定, 一个
GUI环境没有控制台区 。
2004.11.3 AI程序设计 49
第 二 部分:第 7章 Visual Prolog编程
7.2.3 开始一个 GUI项目
? 在一个控制台应用程序中, 控制台总是可以作为一个黑板来使用, 程序员可
以在其上输出信息 。 这就是为什么这些应用程序被称为控制台应用程序的原
因 。 在前面的章节里可以看到, 在这样的应用程序中, 我们直接使用
stdio::write(…)谓词向控制台输出 。
? 在这一阶段运行所编译的 GUI程序时, 只可以空阅程序的菜单, 改变程序外
部主窗口 ( 即任务窗口 ) 的大小, 双击 Message窗口在任务窗口之中进行
全程缩放等等 。 Visual Prolog为 Message窗口恰好给出一个小的常常是很
方便的弹出式菜单 。 在 Message窗口内部任何地方右击鼠标, 随即就会弹
出一个小菜单, 可以清除 Message窗口的内容或进行其他的活动 。
2004.11.3 AI程序设计 50
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 现在, 我们给程序添加另一个 GUI组件, 供以后需要时使用 。 这个组件
是一个对话框, 它用来给程序提供要处理的一个人的名字 。 在项目树
( project tree) 中, 所有模块和资源都清晰地展现在树形菜单里, 右
击任务窗口, 从弹出菜单的上下文菜单中选取, New…”菜单项, 如图
7.10所示 。
图 7.10 激活创建项目条款对话框
2004.11.3 AI程序设计 51
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 创建项目条款的对话框 Create Project Item出现, 如图所示 。 确保对话
框左面的 Diaglog选项被选中, 并且在右面输入如图 7.11所示的详细信息 。
图 7.11 创建项目条
款对话框
2004.11.3 AI程序设计 52
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 这样, 一个缺省的对话框被创建了出来, 如图 7.12所示, 可供进一步编辑
加工之用 。 因为我们没有为这个对话框实现帮助功能, 所以就可以点击一
下特定的 GUI组件 ( 例如 Help按钮 ), 再按下 DEL键即可将其删除 。
图 7.12 创建的缺省对话框
2004.11.3 AI程序设计 53
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 然后,再分别选中其他两个按钮,移动到合适的位置,最后的结果变成
如图 7.13所示的那样。
图 7.13 编辑缺
省对话框
2004.11.3 AI程序设计 54
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 通过使用对话框控件, 我们给对话框插入一个静态文本 。 所谓静态 ( static)
一词代表一个 GUI元素是不可点击的 。 参考前面提到的图像, 在点击静态文
本创建按钮之后, 可以在对话框中画一个矩形区域, 所要输入的文本文字
将出现在其中, 如图 7.14所示 。
图 7.14 在缺省对话
框中增加控件
2004.11.3 AI程序设计 55
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 随之编辑控件属性
( Edit Control
Attributes)将出现,
如图 7.15所示。在
Text字段下,输入
,Person”,所输入
的文本将出现在对话
框里面。
图 7.15 编辑控件属
性对话框
2004.11.3 AI程序设计 56
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 以类似的方式, 我们使用对话框编辑控件来插入一个可编辑的文本字段
或一个编辑控件, 如图 7.16所示 。
图 7.16 编辑控件工具栏
2004.11.3 AI程序设计 57
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 此时, 我们先放下那个
空的文本字段, 转去改
变常量 ( Constant)
符号为某个有意义的符
号常量, 而不是由
VDE提供的缺省常量,
这个步骤如图 7.17所示 。
这个常量在程序代码内
部使用, 以便程序代码
引用这个编辑字段 。
图 7.17 在编辑控件属性对话框中改变常量
2004.11.3 AI程序设计 58
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 我们构造的对话框的最终的情形,如图 7.18所示
图 7.18 所创建的对话框
2004.11.3 AI程序设计 59
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 每一个对话框有一个属性称为访问次序 。 我们需要完成的最后一步, 就是设
置这个控件在对话框中被访问的次序, 即当用户按下 TAB键时, 对话框中的
GUI组件与用户交互的次序 。 短语, 访问控件 ( Visit a control), 意思是
说控件获得输入焦点, 这是一个十分专业的术语 。 短语, 控件接收输入焦点
(the control receiving the input focus)”意思是说控件立即从键盘接收
输入 。 例如, 如果一个编辑字段具有输入焦点, 就可以看到该编辑字段中闪
烁的插字符号, 表示该字段准备接收经由键盘输入的字符 。
? 为了改变访问次序, 右击该对话框, 在随即弹出的菜单中选取访问次序
( Visit Order) 菜单项 。
? 一些小按钮将出现在该对话框的 GUI组件上, 如下图 7.19所示 。 在这里可以
看到, 文本编辑控件 ( idc_ancestordialog_personname) 的访问次序
为 3,而 OK按钮的访问次序编号为 1。
2004.11.3 AI程序设计 60
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 这就是说, 当该对话框呈现
给用户的时候, 该文本编辑
控件将不会立即接收键盘的
字符 。 当用户按一下 TAB键
时, 焦点将转移到 Cancel按
钮, 只有再一次按下 TAB键
时, 焦点才转移到该文本编
辑控件 。 只有到了这个时候,
该文本编辑控件才接收键盘
的输入 。
? 简言之, 相对于该对话框中
的其他控件而言, 编辑字段
的访问次序编号是最后一个 。
图 7.19 确定对话框上控件的访问次序
2004.11.3 AI程序设计 61
第 二 部分:第 7章 Visual Prolog编程
7.2.4 创建模态对话框
? 使用同样的对话框属性 ( Dialog Attributes) ( 参见图 7.20所示 ),
我们也可以把标题改为, Ancestor of,..”。
图 7.20 对话框属性
2004.11.3 AI程序设计 62
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 现在我们将修改程序的主菜
单 。 如前所述, VDE已经
提供了一种在许多程序中都
可见到的由标准菜单项组成
的缺省菜单 。 我们要去编辑
它以满足程序的功能需求 。
通过项目树, 双击
TaskMenu.mnu项, 可以
看到如图 7.21所示的情形 。
图 7.21 双击任务菜单项
2004.11.3 AI程序设计 63
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 这将开始菜单编辑器 。 菜单编辑器的主对话框如图 7.22所示 。 确保在主目
录中选定编辑菜单 ( Edit) 后, 点击属性 ( Attributes) 按钮 。
图 7.22 菜单编辑器
2004.11.3 AI程序设计 64
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 接下来就打开了菜单项属性 Menu Item Attributes对话框, 如图 7.23所
示 。 我们可以从 &Edit到 &Query改变名字, Q前面的, &”标记指在主菜
单中该字母下面有下划线, 用户能通过该字母迅速进入菜单 。
? 我们可以点击上述对话框中的, Test”按钮来测试这个特性, VDE的顶端
菜单将会暂时被现在设计的菜单所替换,
然后我们就可以浏览,感
觉其效果。按 ESC键就可
以返回 VDE菜单的原来状
态。
图 7.23 菜单项属性对话框
2004.11.3 AI程序设计 65
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 我 们 现 在 回 到 主 任 务 菜 单
( TaskMenu) 对话框 。 双击
&Query入口, 我们会注意到
它仍然包含老的 Edit菜单里的
菜单项 (Undo,Redo,Cut,
Copy与 Paste)。 从主菜单条
目入口 &Query删除在里面看
到的所有菜单项 。 接着把常量
前缀 Constant Prefix设定为
id_query 。 常量前缀
Constant Prefix由 VDE内部使
用, 用于描述表示各种菜单项
的常量 。 这些常数被用来在代
码内引用菜单项 。
图 7.24 在任务菜单对话框设定常量前缀
2004.11.3 AI程序设计 66
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 现在我们要在 Query主菜单下增加一些菜单条目。点击 New( 如图 7.24
所示)按钮,然后输入信息,进入图 7.25所示的界面。
图 7.25 利用菜单项属性增加子菜单
2004.11.3 AI程序设计 67
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 最后一步是 TaskWindow菜单的编辑 。 在缺省状态, 当 VDE创建菜单时,
File|Open 菜 单 无 法 使 用, 必 须 找 出 该 菜 单 项, 从 Menu Item
Attributes对话框中去掉禁止标记, 使它变为可用, 如图 7.26所示 。
图 7.26 菜单项
属性对话框
2004.11.3 AI程序设计 68
第 二 部分:第 7章 Visual Prolog编程
7.2.5 修改菜单
? 当关闭 TaskMenu对话框时,VDE会请求确认是否想保存这个菜单。点
击 Save键保存。
图 7.27 VDE确认对话框
2004.11.3 AI程序设计 69
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 工具栏的应用是 GUI的另一个有用部分 。 通常, 它包括具有各种菜单功
能的按钮, 简单地说, 这些按钮就是这些菜单的快捷方式 。 现在我们来
编写工具栏的程序 。 当开始创建一个项目时, Visual Prolog VDE会为
程序创建一个缺省的工具栏 。 在项目树中双击 ProjectToolbar.tb,如
图 7.28所示 。
图 7.28 在项目树中双击工具栏项
2004.11.3 AI程序设计 70
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 这将调用工具栏编辑器,如图 7.29所示。
图 7.29 项目工具栏编辑器
2004.11.3 AI程序设计 71
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 在上图中, 顶端是我们正在编辑的工具栏, 底端是编辑工具栏组件可以
用到的各种控制按钮 。
? 在工具栏中, 包含一组有预定图标图形的按钮 ( 与一般 GUI程序一样 ) 。
如果我们希望, 也可以改变这些图标的图形 。 需要指出的是, Visual
Prolog的 VDE内部已经恰当地包含了一个非常好的图标编辑程序, 对大
的图标, VDE打开 MS画图工具来编辑 。
? 这些按钮已经被订制为一套功能菜单项 。 但是当我们在编辑菜单项时,
也要编辑工具栏按钮并且把它放在适当的位置 。
? 首先, 我们对剪切, 拷贝和粘贴等按钮进行删除处理, 因为程序没有这
些功能 。 为此, 首先选择这些按钮, 然后从工具栏中删除 。 删除以后,
工具栏如图 7.30所示 。
图 7.30 对话框属性
2004.11.3 AI程序设计 72
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 现在我们将把 Undo,Redo 和 Help按钮映射并表示为下列菜单项
Query|Father...,Query|Grandfather..,和 Query|Ancestor of,..。
? 我们在前面提到过, 在本章中我们将不改变那些按钮的图标图像 。
? 双击 Undo工具栏按钮, 在出现的按钮属性 Button Attributes对话框内,
将象征工具栏按钮的内部常量从 id_edit_undo 变为 id_query_father,
如图 7.31所示 。
图 7.31 按钮属性
对话框
2004.11.3 AI程序设计 73
第 二 部分:第 7章 Visual Prolog编程
7.2.6 修改工具栏
? 在同一个对话框中, 还应该将 Status Text设置由
Undo;Undo
变为
Query fathers;List of all fathers listed in the database
? 上一行的分号将字符串分成两部分, 第一部分作为按钮本身的工具提示而显
示, 而第二部分将在主窗口的状态行显示 。
? 运用同样的方法, 改变 Redo按钮的常量值为 id_query_grandfather,改
变 Help按钮的常量值为 id_query_ancestor_of。 这两个按钮的状态行内
容也做适当的修改 。
2004.11.3 AI程序设计 74
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 我们已经完成了程序中需要的全部 GUI功能性工作 。 现在我们将开
始插入逻辑代码 。
? 在做这些工作之前, 我们来了解常用的程序运行方式和现在的 GUI
程序运行方式之间的一些重要差异 。
? 模块的内部已经包含了初级逻辑代码 。 但是现在依靠相应的 GUI组
件控制的逻辑, 它将被扩展到几个模块 。
2004.11.3 AI程序设计 75
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 让我们来增加一些程序
必需的逻辑代码。打开
TaskWindow.pro,
将插字编辑符定位在下
面的代码行,
facts
thisWin,
vpiDomains::windo
wHandle,=
erroneous,
图 7.32 任务窗口代码
2004.11.3 AI程序设计 76
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
? 一旦找到那一行代码且设置了插字编辑符(见图 7.32),就在那里插入下列
代码,
domains
gender = female(); male(),
class facts - familyDB
person, (string Name,gender Gender),
parent, (string Person,string Parent),
class predicates
father, (string Person,string Father) nondeterm anyflow,
clauses
father(Person,Father),-
parent(Person,Father),
person(Father,male()),
2004.11.3 AI程序设计 77
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
class predicates
grandFather, (string Person,string Grandfather) nondeterm
anyflow,
clauses
grandfather(Person,Grandfather),-
parent(Person,Parent),
father(Parent,Grandfather),
class predicates
ancestor, (string Person,string Ancestor) nondeterm
anyflow,
clauses
ancestor(Person,Ancestor),-
parent(Person,Ancestor),
2004.11.3 AI程序设计 78
第 二 部分:第 7章 Visual Prolog编程
7.2.7 在程序中添加主代码
ancestor(Person,Ancestor),-
parent(Person,P1),
ancestor(P1,Ancestor),
class predicates
reconsult, (string FileName),
clauses
reconsult(Filename),-
retractAll(_,familyDB),
file::consult(Filename,familyDB),
? 上 面 的 代 码 是 程 序 的 核 心 逻 辑 部 分 。 在 本 节, 我 们 直 接 将 他 们 插 入
TaskWindow.pro模块中, 因为我们想了解 GUI组件如何进入到程序的核心逻辑中
执行各种动作 。 在更复杂的例子中, 核心逻辑代码会分开存放, 有时分散到好几个模
块中 。 GUI模块 ( 如 TaskWindow) 通过扩大模块的范围, 分别引用这些模块 。
? 事实上, 让程序的核心逻辑代码尽可能地分散到所有 GUI相关的模块中是一个很好的
实践, 但在本节中我们将有意忽略这些 。
2004.11.3 AI程序设计 79
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 既然已经考虑了核心逻辑, 现在我们需要插入交互位 ( interactive
bits) 。 在项目树中右击 TaskWindow.win条目, 该条目表示该主任务
窗口的窗口资源 。 所有发生在这个窗口的点击, 菜单等事件都由写给这
个窗口的事件处理器处理 。 右击所选的项, 就会弹出如图 7.33所示的上
下文菜单, 从该菜单选择代码专家 Code Expert。
2004.11.3 AI程序设计 80
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
图 7.33 在右键弹
出菜单中选择
2004.11.3 AI程序设计 81
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对话框和窗口专家 (Dialog
and Window Expert)
( 如图 7.34所示 ) 帮助我
们为特定窗口或对话框控
件 ( 交互式 GUI组件 ) 设
置缺省的事件处理器 。 蓝
色填充的圆圈表示没有给
定控件的事件处理器, 绿
色的钩记号表示存在相关
的事件处理器 。
图 7.34 对话框与窗口专家
2004.11.3 AI程序设计 82
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 使用上面的对话框, 确保事件处理器是为常量 id_file_open所表示的菜单
项设置的 。
? 现在, 在项目树中点击 TaskWindow.pro模块, 选中它, 用 Build菜单来编
辑它 。 如果 TaskWindow.pro模块没有被选择, 那么 Build菜单中的
Compile菜单将不可用, 因此一定要谨慎 。
? 当用 Visual Prolog 6编辑模块时, 它要改造项目树, 使所有相关的谓词 /论
域等清晰地展开, 便于我们直接访问 。 因此我们要使用这些特点来保证
VDE列出在 TaskWindow.pro模块中定义的所有谓词 。 然后即可对我们关
心的谓词进行操作 。
2004.11.3 AI程序设计 83
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 注意, VDE自动识别出 TaskWindow.pro需要一些附加模块, 如图 7.35所
示的那样, 对该模块的编译进行了两遍 。
图 7.35 消息窗口
2004.11.3 AI程序设计 84
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 编译完成以后,当我们引用 TaskWindow谓词时,会在项目树中看到所
有的谓词,如图 7.36所示。
图 7.36 项目树中的谓词
2004.11.3 AI程序设计 85
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 我们会发现 taskwindow.pro模块的谓词部分将显示出谓词 onFileOpen,
而原来它是不存在的 。
? 双击该谓词, 就会直接打开编辑器, 并定位于谓词 onFileOpen的子句位置
处 。 当该谓词有多重子句时, 指针将指向第一个子句体 。
? 谓词 onFileOpen被称作事件处理器 ( windows应用程序术语 ), 作为程序
员, 不需要访问这个谓词 。 对应的 GUI组件被激活时, windows会自动调
用它 。 在这里单击该菜单项, 缺省情况下为事件处理器插入了如下代码,
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 86
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 用下面的代码替换该代码如下,
onFileOpen(_MenuTag) = handled(0),-
Filename = vpiCommonDialogs::getFileName(
"*.txt",["Family data files (*.txt)","*.txt","All files",
"*.*"],
"Load family database",
[],".",_),
!,
reconsult(Filename),
stdIO::writef("Database % loaded\n",Filename),
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 87
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对于对话框和窗口专
家 ( Dialog and
Window Expert),
确保 Query |
Father,Query |
Grandfather和
Query | Ancestor
of,..菜单项的事件
处理程序设置如图
7.37所示。
图 7.37 设置菜单项
的事件处理程序
2004.11.3 AI程序设计 88
第 二 部分:第 7章 Visual Prolog编程
7.2.8 压缩相关代码
? 对于 Query|Father菜单项, 在 Visual Prolog 自动生成的子句体前添加如
下子句体,
onQueryFather(_MenuTag) = handled(0),-
stdIO::write("\nfather test\n"),
father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
? 对于 Query|grandFather菜单项, 在 Visual Prolog自动生成的子句体前
添加如下代码,
onQueryGrandFather(_MenuTag) = handled(0),-
stdIO::write("\ngrandFather test\n"),
grandfather(X,Y),
stdIO::writef("% is the grandfather of %\n",Y,X),
fail,
2004.11.3 AI程序设计 89
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 现在, 也许我们会问, 哪里是封装? 其实, 我们已将代码分成两部分, 前面
所讲到的是非交互式的逻辑核心 。 而这部分需要接收用户的输入, 并且这些
部分被存储为不同的事件处理器, 至于其他的程序, 它并不需要知道这些事
件处理器在程序中怎样编写, 下面就让我们添加一些交互代码到其他事件处
理器中 。
? 在 Visual Prolog自动产生的缺省子句体前, 为 Query | Ancestor of...菜
单项增加如下子句体,
onQueryAncestorOf(_MenuTag) = handled(0),-
X = ancestorDialog::getName(thisWin),
stdIO::writef("\nancestor of % test\n",X),
ancestor(X,Y),
stdIO::writef("% is the ancestor of %\n",Y,X),
fail,
2004.11.3 AI程序设计 90
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 上面代码的目的就是从前面已经构造的 ancestorDialog所提供的模态对
话框中获取一个字符串 。 代码的谓词部分假设有一个全局可达的叫做
getName的谓词, 该谓词在 ancestorDialog模块中可用, 它将会返回某
人的名字, 这个人的祖先正是我们要寻找的对象 。
? 与在 onFileOpen 事件处理器中看到的一样, 我们寻找一个从模态对话框
返回的字符串 ( 文件名 ) 。 模 态 对 话 框 本 身 被 谓 词
vpiCommonDialogs::getFileName(...) 调用 。 它与我们从
ancestorDialog获得字符串的过程采用了相同的策略 。 惟一不同的是:
vpiCommonDialogs::getFileName(...) 提供了一个内置的标准的窗口
模式对话框文件, 但是对于自己家族的 ancestorDialog,我们不得不做
更多的编码工作 。
2004.11.3 AI程序设计 91
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 编译这个程序时, 可能会出现一个错误, 如图 7.38所示 。
? 错误的原因是因为谓词 onQueryAncestorOf所期盼的全局可达谓词
gerName从 ancestorDialog.pro模块进行调用 。 但是还没那样写, 下面
让我们来纠正这些错误 。
图 7.38 编译错误信息
2004.11.3 AI程序设计 92
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
? 这个谓词在一个模块中定义, 但是在其他模块中调用, 因此需要我们确保这种
声明不保存在,pro文件中, 但是放在一个所有程序都能调用的位置 。 模块的
类声明文件 (extension.cl) 就是这样的位置 。 所以, 让我们打开
ancestorDialog.cm,添加下面的代码 ( 这个声明表示 getName谓词在模块
内执行, 也能被其他模块调用 ) 。
predicates
getName, (vpiDomains::windowHandle Parent) -> string
Name determ,
? 在模块 ancestorDialog.pro 里, 添加该模块的相关核心逻辑 。 与
Taskwindow.pro设计的方式一样, 就是将这些代码插入下面的代码行前,
facts
thisWin, vpiDomains::windowHandle,= erroneous,
? 下面就是要插入的代码,
2004.11.3 AI程序设计 93
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
domains
optionalString = none(); one(string Value),
class facts
name, optionalString,= none(),
clauses
getName(Parent) = Name,-
name,= none(),
Dlg = ancestorDialog::new(),
Dlg:show(Parent),
one(Name) = name,
? 现在还有最后一个问题 。 我们需要改变 OK按钮的事件处理器, 这是必须的,
以便对话框把由用户输入的值声明进 name事实类 。 不做这些, 上面的谓词
将会发现一个空的字符串 。
Visual Prolog给出的缺省代码如下,
2004.11.3 AI程序设计 94
第 二 部分:第 7章 Visual Prolog编程
7.2.9 分析所做的工作
predicates
onControlOK, vpiDomains::controlHandler,
Clauses
onControlOK(_Ctrl,_CtrlType,_CtrlWin,_CtrlInfo) = handled(0),-
vpi::winDestroy(thisWin),
上面的代码将被改写如下,
predicates
onControlOK, vpiDomains::controlHandler,
clauses
onControlOK(_Ctrl,_CtrlType,_CtrlWin,_CtrlInfo) = handled(0),-
EditCtrl = vpi::winGetCtlHandle(thisWin,
idc_ancestordialog_personname),
Name = vpi::winGetText(EditCtrl),
name,= one(Name),
vpi::winDestroy(thisWin),
? 现在我们已经完成了程序,如果现在编译并运行这个程序,将不会有任何错误。
2004.11.3 AI程序设计 95
第 二 部分:第 7章 Visual Prolog编程
7.2.10 运行程序
? 一旦开始执行程序, 要注意到不会立即执行这些活动 。 在前面, 我们已经介
绍了控制台程序的工作方式 。 像前面说的一样, 开始一个 GUI程序就像要进
入一个房间, 在那里用户可以自由地完成他选择的任何事情 。 每个房间仅仅
是等待用户决定给出的输入 。
? 类似地, 在这小程序中, 我们能不装载任何数据调用 Query|Query
Father菜单 。 它不会产生任何结果或错误, 因为我们的主要逻辑只关心缺
省数据的情况, 我们可以在选项中调用 File|Open…菜单, 并装入 family数
据库 ( 前面的教程中用了同样的一部分 ) 。
? 之后, 我们可以测试 Query | Query Father,Query|Query Ancestor
和 Query | Ancestor of...来查看控制台程序获得的上述结果 。 这些结果
将被显示在消息窗口 ( 注意不要关闭消息窗口, 否则结果不会被显示出来 ) 。
当我们发现可以多趟运行询问时, GUI程序将被认可, 我们可在任何时间点
上自由地调用不同数据 。
2004.11.3 AI程序设计 96
第 二 部分:第 7章 Visual Prolog编程
7.2.11 小结
? 在这一节, 我们已经研究了 GUI的基本元素以及如何用 Visual
Prolog开发一个 GUI程序 。 我们发现 Visual Prolog给出的 VDE允
许我们完全控制我们希望使用的所有 GUI组件 。 我们可以编辑菜单,
对话框和工具栏, 以满足所希望的功能 。
? 我们可以模块化 Prolog代码, 并且将代码插入程序的不同部分, 以
便他们被安全地封装到不同的事件处理器中 。 非交互式逻辑核心被
分别保存, 因此开发的 GUI程序可以被灵活使用, 也不强迫用户关
心程序执行活动的顺序 。
返回
2004.11.3 AI程序设计 97
第 二 部分:第 7章 Visual Prolog编程
7.3 Visual Prolog的逻辑层
在这一节, 我们将修改在上一节开发的简单 GUI( 图形用户接
口 ) 程序, 并将程序的逻辑从其余的 GUI代码中隔离出来 。 这一
代码逻辑会被放入一个属于自己的程序包的对象内 。 而隔离代码
的这一个方法会使我们清楚地从 GUI中去思考 ( 代码 ) 逻辑问题 。
2004.11.3 AI程序设计 98
第 二 部分:第 7章 Visual Prolog编程
7.3 Visual Prolog的逻辑层
7.3.1 初始准备阶段
7.3.2 创建业务逻辑层
7.3.3 在业务逻辑层上工作
7.3.4 创建业务逻辑类
7.3.5 理解业务逻辑类
7.3.6 连接业务逻辑层到 GUI
7.3.7 理解事件处理程序
7.3.8 运行代码
7.3.9 细节考虑
7.3.10 小结
2004.11.3 AI程序设计 99
第 二 部分:第 7章 Visual Prolog编程
7.3.1 初始准备阶段
? 在这一节中, 我们需要以正确的步骤安装项目的 GUI,完全像我们在上一节
中所说明的步骤那样 。 我们假定, 读者已经正确完成了那些 GUI构造过程,
因此这里不再重复 。
? 注意:我们假定读者已经在 GUI中创建了 AncestorDialog对话框, 并且已
给定了代码, 因此该模块已经定义了谓词 getName来检索在该对话框输入
的祖先的名字 。
? 我们假设该项目名字为 family3。
? 程序的 GUI在技术上著称为表示层 。 较早的时候, 逻辑代码被混合在表示层
内 。 现在, 我们将取出该逻辑并把它放入项目内自己的程序包中 。 此程序包
可用作业务逻辑层 (business logic layer)。
2004.11.3 AI程序设计 100
第 二 部分:第 7章 Visual Prolog编程
7.3.1 初始准备阶段
? 注意, 我们在使用句子, business logic” 而不是, logic”,因为它指示程
序的, business” 层面 。 简言之, 程序存在的理由来自于逻辑层 。
? 因为 Visual Prolog 6是一种强有力的面向对象语言, 业务逻辑会被表示为
一个对象 。 关于深入地理解 Visual Prolog 6面向对象编程的细节, 读者可
参见, 类与对象, 一章中的详细解释 。 但为了完整起见,有关面向对象的一
些思想在本节中也会提到 。 而在此之前, 让我们逐步完成程序 。
2004.11.3 AI程序设计 101
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 一旦安装好 family3项目的 GUI,就会看到如图 7.39所示的主项目目录。
图 7.39 family3的项目树
2004.11.3 AI程序设计 102
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 右击目录的顶端,从弹出的菜单中点击 New菜单项,如图 7.40所示。
图 7.40 在 family3项目树中的右键菜单
2004.11.3 AI程序设计 103
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 从出现的创建项目条款 Create Project Item对话框, 如图 7.41所示,
选择 Package (左边列表的第一项 ),并且给定 package的命名为
familyBLL( 为使家庭业务逻辑层容易记忆, 应该选择有意义的名字 ) 。
图 7.41 在创建项目对话
框键入程序包的名字
2004.11.3 AI程序设计 104
第 二 部分:第 7章 Visual Prolog编程
7.3.2 创建业务逻辑层
? 单击创建 Create按钮,项目目录呈现出的内容如图 7.42所示。
如果现在查看硬盘上存储这些文件的的文件夹, 那么就会看到, VDE已经
在主项目文件夹内创建了一个单独的文件夹 ( 叫做 familyBLL) 存储某一程序
包的所有文件 。
现在, 建立程序, 以确保工作有序完成 。
图 7.42 在 family3项目
树中的新内容
2004.11.3 AI程序设计 105
第 二 部分:第 7章 Visual Prolog编程
7.3.3 在业务逻辑层上工作
? 让我们在适当的位置放置逻辑 。 我们将充分利用 Visual Prolog 6面向对象
的特性产生且设计 familyBLL程序包的一个 familyBL类 。 在某些叙述中,
有些特征并没有说明 。 然而, 这里我们将使用面向对象系统的主要特征之一,
即数据封装 。
? 数据封装这一术语指将数据隐藏在某一特定模块的代码内, 程序的其他模块
不能直接读取或修改其中的数据 。 数据存取被严格控制, 并且数据只能间接
地通过该类的谓词读取或修改 。
? 这一原理避免了许多程序设计语言中最普遍犯的程序设计错误 。 当数据被保
存下来为项目的所有程序设计者所读写时, 程序设计时常无法快速写出访问
代码和改变此类的数据 。 这是非常成问题的, 尤其是在非同步的程序中, 比
如 GUI程序, 数据在哪里改变无法预知;时常依照人的行为使用程序 。
? 数据封装的另外一个优点是内在的数据表达系统能随时在类内被修正, 项目
内其余的代码不会受到影响 。
2004.11.3 AI程序设计 106
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 在项目目录中的 familyBLL程序包文件夹上右击 。 从打开的菜单上面
点击 New…,会看到如图 7.43所示的情形 。
图 7.43 在 family3项目
树中的新内容
2004.11.3 AI程序设计 107
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 从出现的创建项目条
款 Create Project
Item对话框, 创建
一个名为 familyBL
的类, 确保 Creates
Object选项框选中,
可以看到如图 7.44所
示的情形 。
图 7.44 为项目创建 familyBL类
2004.11.3 AI程序设计 108
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 单击 Create按钮, VDE将会给出 familyBL.pro文件让编辑 。 替换从 implement到
end implement间的缺省代码为下列代码,
implement familyBL
open core
constants
className = "FamilyBLL/familyBL",
classVersion = "$JustDate,$$Revision,$",
clauses
classInfo(className,classVersion),
facts
fileName, string,
clauses
new(Filename),-
filename,= Filename,
file::consult(Filename,familyDB),
2004.11.3 AI程序设计 109
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
clauses
save(),-
file::save(filename,familyDB),
facts - familyDB
person, (string Name,gender Gender),
parent, (string Person,string Parent),
clauses
father(Person,Father),-
parent(Person,Father),
person(Father,male()),
clauses
grandfather(Person,Grandfather),-
parent(Person,Parent),
father(Parent,Grandfather),
clauses
ancestor(Person,Ancestor),-
parent(Person,Ancestor),
ancestor(Person,Ancestor),-
parent(Person,P1),
ancestor(P1,Ancestor),
end implement
2004.11.3 AI程序设计 110
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 现在, 点选 VDE的 File | Open的菜单项, 打开文件 familyBL.i,用下面的
代码替换看到的缺省代码,
interface familyBL
open core
domains
gender = female(); male(),
predicates
father, (string Person,string Father) nondeterm (o,o) (i,o),
predicates
grandFather, (string Person,string Grandfather) nondeterm
(o,o),
predicates
ancestor, (string Person,string Ancestor) nondeterm (i,o),
predicates
save,(),
end interface
2004.11.3 AI程序设计 111
第 二 部分:第 7章 Visual Prolog编程
7.3.4 创建业务逻辑类
? 由 VDE自动生成的很多文件需要被修正 。 点选 VDE的 File|Open菜单项,并
且 打开 familyBL.cl文件 。
? 在文件中见到的谓词段之前, 需要插入以下代码,
constructors
new, (string Filename),
? 在插入上述代码之后, 文件会是下面这样,
class familyBL, familyBL
open core
constructors
new, (string Filename),
predicates
classInfo, core,:classInfo,
% @shot Class information predicate,
% @detail This predicate represents inform
% @end
end class familyBL
2004.11.3 AI程序设计 112
第 二 部分:第 7章 Visual Prolog编程
7.3.5 理解业务逻辑类
? 如果比较我们现在写的代码与早期教程中的代码,可能会得到这样的结论,
1)代码被分成三个段。 2)一些谓语和论域被声明在,i文件中。
3)所有的实现被放在,pro文件中。 4),cl文件包含特殊谓语 constructor的
声明。
? 当我们要求 VDE建立一个类的时候,我们指出类会产生对象 (objects)。 区别
一个类和一个对象之间的不同点是很重要的。
? 尽管类是在项目中被编码,但它并不意味着项目自动包含类的对象。在每个
被设计的用于产生对象的类中,那里肯定会有一个或多个特殊的构造函数
constructor,它将构建一个包含所有相关代码和数据的预先封装好的对象。
2004.11.3 AI程序设计 113
第 二 部分:第 7章 Visual Prolog编程
7.3.5 理解业务逻辑类
? 类的构造函数写在,cl文件中。可以自由存取整个类的谓词 (与类产生的对象
相反 ) 也存在于该文件中。这可以在 classInfo实用谓词中看到,它也是在该
文件中声明。
? 创建的每个对象需要有一些可公共存取的谓词操纵封装在它里面的数据。这
样的谓词被分别定义在相关类的,i 文件中。
? 最后,执行文件 (.pro扩展 )将包含实际代码。对于所有的实际用途,代码在
其他模块中是不可见的。
2004.11.3 AI程序设计 114
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在项目树中, 点击 TaskWindow.win项, 选择代码专家 Code Expert,
如图 7.45所示 。 (当 TaskWindow.win在项目目录中被选中的时候,
也可以使用 Ctrl-W 热键 )。
图 7.45 在项目树中点击 TaskWindow.win
2004.11.3 AI程序设计 115
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在代码专家 ( 对话
框与窗口专家 ) 对
话框中, 确保
id_file_open,
id_Query_father,
id_Query_grand
_father 和
id_Query_ancest
or_of 菜单项的缺
省处理程序已经设
置, 就像图 7.46所
示的那样 。
图 7.46 设置菜单的缺省处理程序
2004.11.3 AI程序设计 116
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 正确退出应用程序的 id_file_exit事件处理器已经被 VDE设置, 因此,
不需要再一次设定它 。
? 在我们将正确的代码移植到处理器之前, 显示层应该包含一个事实,
该事实用来存储某时刻点程序处理的对象 。 因此, 在
TaskWindow.pro文件中, 必须给出下列代码,
facts
familyBL_db, (familyBL BL) determ,
2004.11.3 AI程序设计 117
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 上述事实将包含表示 family的业务逻辑层的对象。
? 对于 id_file_open菜单项,将会给出以下事件处理器代码,
onFileOpen(_MenuTag) = handled(0),-
Filename = vpiCommonDialogs::getFileName(
"*.txt",
["Family data files (*.txt)","*.txt",
"All files","*.*"],
"Load family database",
[],".",_),
!,
BL = familyBL::new(Filename),
retractAll(familyBL_db(_)),
assert(familyBL_db(BL)),
stdIO::writef("Database % loaded\n",Filename),
onFileOpen(_MenuTag) = handled(0),
2004.11.3 AI程序设计 118
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 在我们写另一个事件处理器之前,需要插入一个实用谓词如下所示,
predicates
tryGetFamilyBL:()->familyBL BL determ,
clauses
tryGetFamilyBL() = BL,-
familyBL_db(BL),
!,
tryGetFamilyBL() = _,-
stdIO::write("No family database loaded\n"),
fail,
2004.11.3 AI程序设计 119
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 对于 id_Query_Father菜单项, 将给出以下事件处理器代码,
onQueryFather(_MenuTag) = handled(0),-
stdIO::write("\nfather test\n"),
BL = tryGetFamilyBL(),
BL:father(X,Y),
stdIO::writef("% is the father of %\n",Y,X),
fail,
onQueryFather(_MenuTag) = handled(0),
? 对于 id_Query_Grand_Father菜单项目, 将给出以下事件处理器代码,
onQueryGrandFather(_MenuTag) = handled(0),-
stdIO::write("\ngrandFather test\n"),
BL = tryGetFamilyBL(),
BL:grandfather(X,Y),
stdIO::writef("% is the grandfather of %\n",Y,X),
fail,
onQueryGrandFather(_MenuTag) = handled(0),
2004.11.3 AI程序设计 120
第 二 部分:第 7章 Visual Prolog编程
7.3.6 连接业务逻辑层到 GUI
? 最后, 对于 id_Query_Ancestor,将给出以下事件处理器代码,
onQueryAncestorOf(_MenuTag) = handled(0),-
X = ancestorDialog::getName(thisWin),
stdIO::writef("\nancestor of % test\n",X),
BL = tryGetFamilyBL(),
BL:ancestor(X,Y),
stdIO::writef("% is the ancestor of %\n",Y,X),
fail,
onQueryAncestorOf(_MenuTag) = handled(0),
? 观察上面的代码, 我们会注意到三个事件处理器都使用 tryGetFamilyBL()
实用谓词, 试图获得需要的对象, 而不是直接访问事实段 。 在这种情况, 数
据库不被装载, 实用谓词会在消息窗口给出一个适当的消息后失败 。
2004.11.3 AI程序设计 121
第 二 部分:第 7章 Visual Prolog编程
7.3.7 理解事件处理程序
? 如果分析我们现在开发的事件处理程序,并且把它与那些在 GUI中开发的处
理程序作比较,就会注意到他们只有很小的不同。
? 最重要的是在第一个事件处理器 (onFileOpen) 中。程序构造了一个新的
程序事务层类的对象,并且将对象存入该模块的事实 facts段。
? 要注意的另外一个关键点是业务层对象公共存取方法的使用,这些对象已经
存储在 TaskWindow.pro模块的 facts段中。
? 例如,在下列代码片断中,程序获得一个业务逻辑层对象,并赋给变量 BL,
然后运行该对象的公共存取谓词 grandfather。
? 变量和谓词之间的冒号算符,,”是一个逻辑指示器。获得对象公共存取谓词
的冒号算符,,”和获得整个类的公共存取谓词的双冒号算符,,:” 不同。这
可在上面的代码片断 stdIO::writef(...)的谓词调用中看到。需要注意,双
冒号算符不能用在对象上,而冒号算符不能用在类上。
2004.11.3 AI程序设计 122
第 二 部分:第 7章 Visual Prolog编程
7.3.8 运行代码
? 现在,我们能够编译并且运行该程序。如果有错误,那么应该重新
阅读代码,或者也可以查看以前的内容去检查程序中的错误。需要
注意的一点是:当设置事件处理器的时候,不要修改以前已经设定好
的事件处理器的名字。
? 当运行这一程序时,我们会发现程序运行方式和以前的一样。这是
面向对象程序设计的一个好的基础:它允许相当多的内部编码发生
变化但最终的运行功能并不发生变化。
2004.11.3 AI程序设计 123
第 二 部分:第 7章 Visual Prolog编程
7.3.9 细节考虑
? 在面向对象的程序设计系统中, 类事实变成对象事实, 因为每一个对象将
封装它自己的事实 。
? 当我们使用对象表达数据时, 数据的维护变得更加容易 。 如果我们不是用
面向对象的方法, 我们可能需要用 retract谓词 ( retract是 Visual Prolog
6的一个内部谓词 ) 清除剩余的早期数据 。 然而, 现在 retract不再需要,
因为一个新对象建立的同时, 旧的对象会自动被程序通过一个垃圾回收过
程予以清除 。 这个操作在已知的安全条件下, 在后台自动发生, 不会影响
用户 。
? 声明谓词时, 谓词的参数流模式必须要明确地添加到谓词声明中, 就像我
们在,i文件中所做的 。 anyflow流模式不能被用在全局实体上 。
? 在事务层的执行代码中, 我们执行了一个保存数据到一个文件的谓词 。 注
意, 这个保存数据的谓词并不需要文件名, 这是因为文件名存储在对象中 。
下面作为一个练习, 读者可以将这个功能与一些合适的 GUI事件处理器结
合起来 。
2004.11.3 AI程序设计 124
第 二 部分:第 7章 Visual Prolog编程
7.3.10 小结
在本节我们学习到, 业务逻辑及其表示层 ( GUI) 的数据
的较细分离不仅是可能的而且是强烈推荐的 。 Visual Prolog 6
能够智能化地帮助我们创建一个包括所有类的程序包, 这些类用
于管理这些分离的事务 。 在这个特殊教程中, 一个类被开发在一
个程序包中, 这种类可以创建封装数据和业务逻辑的对象 。 其他
模块可以使用这些对象运行与业务逻辑层有关的一些内容 。 这样,
系统维护和软件模块的开发变得更加容易, 减少了出错的可能性 。
2004.11.3 AI程序设计 125
第 二 部分:第 7章 Visual Prolog编程
7.4 Visual Prolog的数据层
在本节, 我们将继续以 family为例来介绍 Visual Prolog能够
更好地处理的下一个抽象层次 —— 数据层 。 本节给出了处理复杂
情况所需的各种组合 。
这里提供的范例项目源文件, 只能运行在 Visual Prolog的
商业版 (Commercial Edition) 环境之下 。
2004.11.3 AI程序设计 126
第 二 部分:第 7章 Visual Prolog编程
7.4 Visual Prolog的数据层
7.4.1 基本概念
7.4.2 程序
7.4.3 非模态对话框
7.4.4 FamilyData包
7.4.5 接口
7.4.6 FamilyDL包
7.4.7 FamilyDL包的代码
7.4.8 FamilyBLL包的代码
7.4.9 数据层的特征
7.4.10 小结
2004.11.3 AI程序设计 127
第 二 部分:第 7章 Visual Prolog编程
7.4.1 基本概念
? 尽管有人说引用的层次越多, 就越容易控制最终结果的实现, 但是
在设计层次以前还是需要仔细的考虑 。
? 如果看到整个 family系列 (Visual Prolog基本层, GUI层, 逻辑层 )
的展开模式, 就会发现:随着引用越来越多的精确的层, 程序的运
行将得到越来越细微的控制 。 同时, 不要有多余的层 。 否则, 它只
能增加程序的复杂性和麻烦性 。
? 在本节, 我们将介绍数据层 ( Data Layer) 。 数据层的核心就是一
个类, 以及使用该类来访问和设置实际数据的对象 。 在上一节, 业
务逻辑层 ( Business Logical Layer,BLL) 也可以处理数据 。 而
在这里, 业务逻辑层 BLL只处理逻辑 。
2004.11.3 AI程序设计 128
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 当在 Visual Prolog的 VDE中安装项目 family4时,就会发现四个独立
的数据包,如图 7.47所示。
图 7.47 family4的项目树
2004.11.3 AI程序设计 129
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 与前面章节中所给出的例子相比, TaskWindow 程序包增加了一个
新的对话框, 即 NewPersonDialog,如图 7.48所示 。 项目中已经包
含了这个对话框, 下面的解释假设要在项目中手动生成该对话框 。
图 7.48 TaskWindow
新增对话框
2004.11.3 AI程序设计 130
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 模态与非模态对话框在创建上没有区别。只是在属性设置上要清楚地指明对
话框的类型,如图 7.49所示。
图 7.49 设置对
话框类型
2004.11.3 AI程序设计 131
第 二 部分:第 7章 Visual Prolog编程
7.4.2 程序
? 光创建这样一个对话框是没用的,
TaskWindow必须能够调用它。
这样我们就需要插入一个新的菜
单项目和与之相关的事件处理程
序。 一旦我们创建了一个适当
的菜单项目,代码专家将允许我
们创建一个处理程序来添加必要
的代码,如图 7.50所示。
图 7.50 设置事件处理程序
2004.11.3 AI程序设计 132
第 二 部分:第 7章 Visual Prolog编程
7.4.3 非模态对话框
? 非模态对话框是一个概念问题 。 这个程序通过使用一个非模态对话
框来收集 family数据库中新增人员的信息 。 前面曾指出, 对应于由
非模态对话框引起的事件发生的代码有时具有欺骗性 。 如果我们了
解模态对话框的工作方式就不难理解这一点了 。 在模态对话框中,
操作系统暂停由同一应用程序中所有其他的 GUI部分所引起的事件
发生, 只将注意力放在由模态对话框所引起的事件发生上 。 在时间
逻辑上程序员需要处理的内容就变得很简单了, 因为程序员很清楚,
与此同时不会有同一程序其他部分复杂事件的发生 。
2004.11.3 AI程序设计 133
第 二 部分:第 7章 Visual Prolog编程
7.4.3 非模态对话框
? 另一方面, 当使用一个非模态对话框时, 程序不仅可以容纳由程序其他部分
引起的事件发生, 也可以容纳由对话框引起的事件发生 。 这样程序员就需要
谨慎地考虑事件处理的排列 ( permutations) 与组合 ( combinations) 。
例如, 程序打开一个数据库, 然后再打开一个该数据库下的一个非模态对话
框 。 这样当数据库关闭而对话框没有关闭时, 对话框的使用就有可能产生错
误 。 当然, 除非程序员已经预料到事件发生的过程并针对这种情况进行设计 。
? 在 NewPersonDialog.pro中, 如果看到了用于处理由对话框引起的事件发
生的源代码, 就会发现该代码捕捉到很多错误发生的情形 。 它甚至有一个完
整性检查器用于跟踪可能导致一段代码产生错误的原因 。
2004.11.3 AI程序设计 134
第 二 部分:第 7章 Visual Prolog编程
7.4.4 FamilyData包
? 在这个例子当中,我们将创建一个 FamilyData包,它包含了用于处理程序
所需论域的所有必需代码。当业务逻辑层与数据层通信时,相同的论域可以
访问 BLL (Business Logic Layer)。
在这个类中,我们编写如下代码,
class familyData
open core
domains
gender = female(); male(),
domains
person = string,
person_list = person*,
domains
optionalPerson = noPerson(); person(person Person),
2004.11.3 AI程序设计 135
第 二 部分:第 7章 Visual Prolog编程
7.4.4 FamilyData包
predicates
toGender, (string GenderString)
-> gender Gender,
predicates
toGenderString, (gender Gender)
-> string GenderString,
predicates
classInfo, core::classInfo,
end class familyData
? 注意, 上面 的类 中包 含了 对多 个类 可共享 的论 域的 定义 。 因此,
FamilyData包担当了数据层和业务层之间会议厅的作用 。
2004.11.3 AI程序设计 136
第 二 部分:第 7章 Visual Prolog编程
7.4.5 接口
? 用来处理数据的层需要经过由 FamilyDL包中的一个类所产生的对象 。 然而,
在获得该包的实际内容以前, 我们首先需要了解接口的概念 。
? 接口可以看作是对与之相应对象的期望意图的描述 。 通过 VDE将它写在一
个单独的文件中, 并包含了能够在类对象中可以找到的谓词 。
? 接口也可以看作是对本节中定义的进一步解释 。 读者将会在后面的内容学到
有关这些接口的高级用法 。
? 将目的相对独立的内容写在一个不同的接口中, 原因很充分:根据一个接口
的声明, 可以写出一个类 。 接口只对类对象所提供的功能作一个简要的定义 。
之所以说简要定义是因为它仅仅对对象的某些功能进行了表达 。 简而言之,
类可以公开声称其对象行为与接口一致 。
? 不同的类可执行同一个接口;每一个类提供一个专门的, 具体的 ( 即非抽象
的 ) 执行 。 这大大减轻了代码维护的负担 。 稍后, 当同一程序中出现更多的
组合情况时, 程序员可以系统地在这些接口上工作 。
2004.11.3 AI程序设计 137
第 二 部分:第 7章 Visual Prolog编程
7.4.6 FamilyDL包
? 这个例子程序还包含了另外一个程序包, 即 FamilyDL包 。 该程序包包含了
主类, 其对象用于处理实际的数据 。
? 该程序包包含一个接口, 即 familydl接口 。 该接口的主要用途是定义由数据
层提供的面向业务逻辑层的谓词 。 同样它在数据层中也起了很重要的作用 。
该接口建立在 FamilyData包中论域的基础上 。
interface familyDL
open core,familyData
predicates
person_nd, (person Name) nondeterm (o),
predicates
gender, (person Name) -> gender Gender,
2004.11.3 AI程序设计 138
第 二 部分:第 7章 Visual Prolog编程
7.4.6 FamilyDL包
predicates
parent_nd, (
person Person,
person Parent)
nondeterm (i,o),
predicates
addPerson, (person Person,gender Gender),
predicates
addParent, (person Person,person Parent),
predicates
personExists, (person Person) determ,
predicates
save, (),
end interface familyDL
2004.11.3 AI程序设计 139
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
? familyDL包有一个接口文件 familydl.i,它的代码在以前的章节中给出过 。 该包中也
有 两 个,pro 文件, 其 中 包 含 了 两 个,cl 类 文 件 的 实 现 。 这 两 个 类 为,
familydl_exception 和 familydl_factfile。 本节中没有 familydl_factfile的代码,
而该代码可以通过检查下载文件 family4.zip来得到 。 该文件中的谓词用于处理数据,
这一点在前面已经提到过 。
? 本节中也没有 familydl_exception 的代码, 而该代码可以在文件 family4.zip中看
到 。 这些谓词包括了支持实用程序的谓词:当程序发现不正确数据或错误 ( 也就是电
脑术语里所谓的异常 ) 时, 实用程序谓词用于显示相应的错误信息 。 很多错误情形的
发生是超出程序员控制范围的 ( 例如, 驱动门被打开就是一个无法预料的异常 ), 但
是有时候需要, 制造, 一个错误 。 也就是说, 程序员能够故意引发 ( raise) 一个错
误, ( 这里用的是计算机界的行话 ) 。 这时, 我们会发现, 在代码中错误实用程序谓
词以术语前缀 raise_开头 。
? 值得注意的是 familydl_exception自身不包括处理异常的逻辑 。 当异常发生时, 应
用程序谓词允许程序显示纠正错误对话框 。
2004.11.3 AI程序设计 140
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
? 下面的例子说明了程序员如何通过故意调用这些 raise_谓词来告知一个错误的情形 。
? 在 familydl_factfile.pro中, 能够找到用于添加父母双亲的谓词 。 在子句当中, 首先
运行两个检查操作, 查看 Person与 Person的双亲 Parent是否已经存在, 然后再调
用 familyDL_exception::raise_personAlreadyExists。 这样就构造了一个异常,
程序会出示一个针对这种情况的一个专门的错误对话框 。 幸运的是, 这个专门的错误
对话框不需要程序员构建, 而是由 Visual Prolog的库在程序连接时生成的 。 程序员
不必像生成其他对话框那样, 专门生成一个错误对话框 。
% note,the following is only partial
% code from familydl_factfile.pro
clauses
addParent(Person,Parent),-
check_personExists(Person),
check_personExists(Parent),
assertz(parent_fct(Person,Parent)),
2004.11.3 AI程序设计 141
第 二 部分:第 7章 Visual Prolog编程
7.4.7 FamilyDL包的代码
clauses
personExists(Person),-
person_fct(Person,_),
!,
predicates
check_personDoesNotExist, (string Person),
clauses
check_personDoesNotExist(Person),-
personExists(Person),
!,
familyDL_exception::raise_personAlreadyExists(
classInfo,Person),
check_personDoesNotExist(_),
2004.11.3 AI程序设计 142
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
? 下面给出 familyBL.i接口文件的代码 。 这与前面章节提到的业务层的接口十
分相似 。 但是, 它们有十分重要的区别:论域已经移动到程序包之外, 在数
据层和业务层之间共享 。 这是一个很显然的移动, 因为在先前的介绍中, 我
们把数据层和业务层当作同一段代码 。 当我们将这两层分开时, 我们仍然需
要某一公共端口, 通过该公共端口, 这两层的代码可以互相对话, 显然我们
需要将这两层的代码使用的论域变成为两者的公共端口 。
? 第二个注意到的变化是谓词不会有多个流程 。 一个谓词只能对它控制的参数
准确地做一件事情 。 这使得程序偏离了传统的 Prolog,变得与其它语言编
写的程序很相似 。 通常, 这个策略产生出更好的结果 。 因为它使人们读源代
码的时候更容易理解程序 。 但是, 先前有多个流程的方法确实会产生更短的
源代码, 而且经常成为那些用过传统 Prolog编程人员的首选方法 。
2004.11.3 AI程序设计 143
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
interface familyBL
open core,familyData
predicates
personWithFather_nd, (
person Person,
person Father)
nondeterm (o,o),
predicates
personWithGrandfather_nd, (
person Person,
person GrandFather)
nondeterm (o,o),
predicates
ancestor_nd, (
person Person,
person Ancestor)
2004.11.3 AI程序设计 144
第 二 部分:第 7章 Visual Prolog编程
7.4.8 FamilyBLL包的代码
nondeterm (i,o),
predicates
addPerson, (
person Person,
gender Gender,
optionalPerson Parent1,
optionalPerson Parent2),
predicates
save, (),
end interface familyBL
? 这里没有给出 familyBL类的源代码 。 一旦将所提供例子的代码装载到 VDE
中, 读者就会读到这些代码 。 这里没有什么新奇的东西 。 它同在先前所介绍
的业务逻辑层中执行的行为是一样的 。 但是, 有两点重要的不同:一旦需要
访问数据或者数据设置, 都会用到 familyDL类中的谓词 。 同时, 检查这个
类的谓词信息的正确性, 当发现逻辑错误的时候, 导致异常产生 。
2004.11.3 AI程序设计 145
第 二 部分:第 7章 Visual Prolog编程
7.4.9 数据层的特征
? 数据层的主要特点是它在面向对象语言环境中实现了一组称之为数据访问器
的谓词 。 数据访问器把数据访问同底层执行分离开来 。 简言之, 可以使用谓
词设置或获得程序所使用的数据 。 实际的数据会保持私有, 导致外部无法访
问 。
? 抛开隐藏属性的具体结构, 数据访问器同样能提供统一的接口来访问数据的
属性 。
? 使用数据访问器的优点在于:在程序的其它部分所实现的算法能独立于数据
层中原来所用的数据的有关知识 。 在以后的内容中, 将会看到在保持原样的
同时, 数据能容易地移入到一个更健壮的系统中, 譬如通过 ODBC层数据访
问来使用微软的 Access数据库 。
2004.11.3 AI程序设计 146
第 二 部分:第 7章 Visual Prolog编程
7.4.9 数据层的特征
? 这种方法几乎不存在任何缺陷 。 即使偶尔存在缺陷, 它们都能被正确理解 。
可能存在的主要缺陷是, 当编写数据层的时候, 程序员在理解上需要达到两
个复杂的层次 。 一个是确定内部数据结构在数据层中保持私有 ( 这种需要开
发数据结构的概念在前面已经讨论过 ) 。 但是在此之后, 程序员仍将必须花
费时间设计数据访问器谓词, 这样其它的层同数据层能平稳地匹配起来 。 当
一组程序员一起工作的时候, 数据访问器谓词不正确的设计通常能产生许多
令人头疼的事情, 尤其是当程序从一个简单的部分移入到一个复杂的部分中
时 。 这些问题都将在以后的内容中看到 。
2004.11.3 AI程序设计 147
第 二 部分:第 7章 Visual Prolog编程
7.4.10 小结
在这一节的程序中, 我们使用了这样一个策略,, 授之以渔,
而不是授之以鱼, 。 这只是将程序的源代码巧妙地模块化, 这样
代码维护与扩展变得特别容易 。 我们把上一节中的业务层分成两
部分:一部分包含纯粹的业务逻辑, 另一部分仅仅是处理数据 。
这要求我们随后去扩充程序, 仅用其他形式的数据处理技术来代
换数据层 ( 例如通过一个 ODBC协议, 或者通过因特网等重新获得
数据 ) 。 这一节同样涵盖了异常处理, 我们能够很容易地编写自
己的异常处理谓词, 然后通过它们向用户显示有用的错误信息 。
2004.11.3 AI程序设计 148
第 二 部分:第 7章 Visual Prolog编程
本章小结
? 本章介绍 Visual Prolog编程方面的知识, 主要内容包括
Visual Prolog编程基础知识, Visual Prolog的 GUI编
程, Visual Prolog的逻辑层编程, Visual Prolog的数
据层编程等 。
? 本章包含了 Visual Prolog编程知识和工程应用程序开发
的十分重要的内容 。
2004.11.3 AI程序设计 149
第 二 部分:第 7章 Visual Prolog编程
习 题
1,传统的 Prolog与 Visual Prolog 6之间的差别主要体现在哪些方面?
2,从结构上讲, Visual Prolog的程序主要包括哪些段? 各段分别有什么功能?
3,按照书中介绍的步骤, 测试完整的例子程序 family1.prj6。
4,GUI程序与控制台程序有何异同?
5,什么叫模态对话框和非模态对话框?
6,根据 7.2节的内容, 学习如何用 Visual Prolog 6开发并运行一个 GUI程序 。 学会菜
单项的设置与相应事件处理程序的结合方法 。 最后实践 7.2节给出的例子
( family2.zip) 。
7,什么叫业务逻辑层? 业务逻辑层及其表示层 ( GUI) 数据的较细分离有什么意义?
8,上机实践 7.3节中将业务逻辑与表示层分离的方法 。 ( family3.zip)
9,将数据处理从业务逻辑层分离的目的是什么? 业务逻辑层与数据层有何关系? 在
Visual Prolog的 VDE中安装项目 family4,实践并研究增加数据层后程序结构, 组
成上等发生的变化 。