2004.11.3 AI程序设计 1
第 二 部分:第 9章 编码风格
第 9章 编码风格
本章介绍 Visual Prolog 6的编码风格, 包括基本
元素, 推荐格式, 程序结构, 程序设计语用学, 存储
管理, 以及异常处理 。 这里描述的 Visual Prolog程序
的编码标准, 是 Visual Prolog系统本身的一部分 。 且
用户文档中的例子也是标准的, 它门同样也代表了
Prolog发展中心为用户推荐的编码标准 。
2004.11.3 AI程序设计 2
第 二 部分:第 9章 编码风格
第 9章 编码风格
9.1 基本元素
9.2 推荐格式
9.3 程序结构
9.4 程序设计语用学
9.5 存储管理
9.6 异常处理
本章小结与习题
2004.11.3 AI程序设计 3
第 二 部分:第 9章 编码风格
9.1 基本元素
9.1.1 关键字
9.1.2 半关键字
9.1.3 文字
9.1.4 标识符
9.1.5 常量
9.1.6 变量
9.1.7 谓词
9.1.8 论域
9.1.9 类和接口
2004.11.3 AI程序设计 4
第 二 部分:第 9章 编码风格
9.1.1 关键字
? 关键字以小写字母表示。在有关资料中,关键字是以没有衬线的粗
体字被编排的,例如 Arial,缺省颜色为暗黄色。例如
constants
domains
facts
predicates
class
interface
2004.11.3 AI程序设计 5
第 二 部分:第 9章 编码风格
9.1.2 半关键字
? Visual Prolog使用了大量的标识以满足多样化的句法结构, 这些词以小写
字母书写 ( 除了 C调用约定写成 C), 且一般是没有衬线的字体 。 这些半关
键字依照它们的性质以两种不同的颜色显示 。 如果这个词表示一种选择, 那
么它显示为藏青色, 而如果它是一种结构词, 那么它将以暗黄色显示 。
erroneous
failure
procedure
determ
nondeterm
multi
stdcall
C
..,
language
as
..,
这个例子显示了颜色和字体 。
predicates
myPredicate, (string
Value)
procedure (i) language
stdcall as "_myP"
2004.11.3 AI程序设计 6
第 二 部分:第 9章 编码风格
9.1.3 文字
? 文字显示为蓝色。 例如
"Hello world!"
2004.11.3 AI程序设计 7
第 二 部分:第 9章 编码风格
9.1.4 标识符
? 标识符的一般格式可以由下面的 EBNF语法来描述,
<Identifier> = <Prefix> <WordGroups> <Suffix>
<WordGroups> = <WordGroup> { ‘_’ <WordGroup> }*
<WordGroup> = <Word> +
? 前缀和后缀被用来表示某种标识符, 并将用来处理各种标识符之间的联系 。
这些词以大写字母书写, 当然除了整个标识符的第一个字母必须小写以外 。
? 所有变量以大写字母开始, 而其他所有的标识符以小写字母开始 。
? 在文件中, 除了关键字, 所有的标识符以衬线字体编排 。 例如 Times New
Roman字体 。
2004.11.3 AI程序设计 8
第 二 部分:第 9章 编码风格
9.1.5 常量
? 常量既没前缀也没后缀,它以小写字母开始。例如
? numberOfRows,pi,logErrorMsg
2004.11.3 AI程序设计 9
第 二 部分:第 9章 编码风格
9.1.6 变量
? 变量也没前缀和后缀 。 像前面提到的 Prolog要求的那样,
变量以大写字母开始 。 在程序文件中变量以绿色显示 。
2004.11.3 AI程序设计 10
第 二 部分:第 9章 编码风格
9.1.7 谓词
? 谓词没有前缀。然而,,try”可以用来表示一个谓词是确定性的,
特别是它被用做从一个相应的程序描述中区分确定性谓词的描述。
而后者将引起一个异常而不是失败。例如,
trySetValue, (integer Value) determ (i),
setValue, (integer Value) procedure (i),
? 除非为了避免混淆必须添加后缀,否则谓词是没有后缀的。在一些
情况下,为避免混淆,表 9-1中的后缀应该被选用。
? 注意:一般来说,多重谓词应以 _nd为后缀,但如果环境需要也可
用 _multi代之。
2004.11.3 AI程序设计 11
第 二 部分:第 9章 编码风格
9.1.7 谓词
表 9-1 常用的谓词后缀
后 缀
意 义 描 述
_db
数据库算符 /谓词
_nd
nondeterm/multi
_err
erroneous
_fail
失败
_det
determ
_multi
multi
2004.11.3 AI程序设计 12
第 二 部分:第 9章 编码风格
9.1.8 论域
? 论域没有前缀, _list被用做列表论域的后缀 。 在多数情况下, 列表
论域没有域名 。 例如, 一个数据库记录是一个值的列表, 但是记录
是列表值首选的一个更好的论域名 。 注意论域以小写字母开始 。 这
同样适用于论域如字符串, 整数等 。 例如
string
value
record
record_list
2004.11.3 AI程序设计 13
第 二 部分:第 9章 编码风格
9.1.9 类和接口
? 类和接口没有前缀 。 例如
String inputFile template inputStream
传统的 COM 接口以, I”开始, 现在这个, I”被保留了下来,
但变成了小写,
iUnknown iDispatch
2004.11.3 AI程序设计 14
第 二 部分:第 9章 编码风格
9.2 推荐格式
这一节考虑程序代码的格式 。 通过格式化, 我们可以表
示折行 ( line breaking), 缩排 ( indentation) 和对齐
( alignment) 。 缩排指行开始处的空格的数量, 而对齐
指非行首字符的排列结构 。
2004.11.3 AI程序设计 15
第 二 部分:第 9章 编码风格
9.2 推荐格式
9.2.1 折行
9.2.2 缩排
9.2.3 对齐
9.2.4 空格字符
2004.11.3 AI程序设计 16
第 二 部分:第 9章 编码风格
9.2.1 折行
折行遵守如下规则,
? 一行通常不应超过 70个字符 。
? 外部句法结构总是在内部结构之前被断开 。
? 不同谓词的子句至少用一个空行分开 。
? 同一谓词的子句不应被一个空行分开 。
? 一个段的关键字之前至少有一个空行 。
? 截断 ( 无论它看起来如何微小 ) 本身应该单独占一行 。
? 一个子句的头在一行 。
2004.11.3 AI程序设计 17
第 二 部分:第 9章 编码风格
9.2.2 缩排
通过缩排, 实现在行首的空格数量 。 缩排遵守如下规则,
? 缩排可由相同的步骤实现 ( 例如 4个空格 ) 。
? 如果一套括号的部件必须被分在几行中书写, 那么在开括号后面必
须立即插入一个折行, 且对于开括号缩排增加一步 ( 没有对齐 ) 。
2004.11.3 AI程序设计 18
第 二 部分:第 9章 编码风格
9.2.3 对齐
? 对齐指的是排列结构,这种结构要么不是一行的开始,
要么是通过缩排规则被对齐成行的一行的开始。
? 对齐没有被使用。
2004.11.3 AI程序设计 19
第 二 部分:第 9章 编码风格
9.2.4 空格字符
? 逗号后面的空格可以被省略 。
? 在算符或列表里面, 逗号后面的空格可以被省略 。
? 在声明谓词, 事实, 常量中,,,, 之前或之后的空格 。
? 括号前后没有空格, 除非这个括号与一个被空格所包围
着的记号相邻, 例如 ‘, - ’,‘, ’ 。
2004.11.3 AI程序设计 20
第 二 部分:第 9章 编码风格
9.3 程序结构
9.3.1 段
9.3.2 类、接口及实现
9.3.3 谓词声明
9.3.4 论域
9.3.5 子句
9.3.6 不确定性循环
9.3.7 Word格式化代码
2004.11.3 AI程序设计 21
第 二 部分:第 9章 编码风格
9.3.1 段
? 段关键字本身就在一行中。如果段关键字被缩排 n步,那
么结构就被缩排 n+1步。段与段之间必须至少有一个空
行将其分开。
clauses
p,
clauses
q,
2004.11.3 AI程序设计 22
第 二 部分:第 9章 编码风格
9.3.2 类、接口及实现
? 开类和闭类本身在一行 。 闭类包含类标识符, 类里面的
段关键字比类本身多缩排一步 。
class specialOutputFile, outputFile
predicates
,.,
end class specialOutputFile
这同样适合其它类型 ( 如, 接口等 ) 。
2004.11.3 AI程序设计 23
第 二 部分:第 9章 编码风格
9.3.3 谓词声明
? 谓词声明总是将一些名字作为变元, 这些名称被格式化为变量 。 模式的声明
是可选择的 。
Predicates
increment, (integer X) -> integer Y,
bubbleSort, (integer_list Input) -> integer_list SortedList,
myPredicate_nd,
(aVeryLongDomainName StrangeFirstParamanter,
anotherLongDomainName PlainSecondParameter)
nondeterm (i,o)
dterm (i,i),
注意, 在开括号和缩排正常加大之后的折行 。 或者所有的变元在一个单行或
者每一个变元分开在不同的行 。
2004.11.3 AI程序设计 24
第 二 部分:第 9章 编码风格
9.3.4 论域
? 算符的参数总是有名称的,
这些名称被格式化为变元 。
如果论域声明被断开成几
行, 那么在等号标记后的
是第一个断行 。 或所有的
算符在一个单行, 或每一
个算符单独在一行 。 或一
个算子的所有参数占据一
个单行, 或每一个参数占
据一个单行 。
Domains
value_list = value*,
value =
int(integer Value);
real(real Value);
str(string Value),
aVeryLongDomainName =
x(interger X); y(integer Y),
anotherLongDomainName =
aFunctorWithManyArguments(
integer X,
integer Y,
integer Z,
integer RedColourComponent,
integer BlueColourComponent,
integer GreenColourComponent),
2004.11.3 AI程序设计 25
第 二 部分:第 9章 编码风格
9.3.5 子句
? 子句头本身在一
行里 。 子句体中
每个调用在一行
中 。 如果子句头
必须被断开, 那
么变元比子句头
多缩排两步, 否
则变元和子句体
将被缩排在同一
位置 。
clauses
myPredicate(X,Y),-
callNoOne(X,Y,Z),
!,
callNoTwo(Z,Y),
myPredicate(X,Y),-
callNoThree(X,Y),
aPredicateWithManyArguments(FirstArgument,
SecondArgument,
X,Y,Z,ErrorNo,ErrorMsg),-
callNoOne(FirstArgument,SecondArgument,X,
Y,Z,
ErrorNo,ErrorMsg),
callNoTwo(…),

2004.11.3 AI程序设计 26
第 二 部分:第 9章 编码风格
9.3.6 不确定性循环
? 在 prolog中常用的一种结构是在不确定性调用结果之上
的循环 。 对这种结构我们打算将循环体缩排为一个额外
的层次 。
clauses,
myPredicate(),-
member(X,[1,2,3,4,5,6])
doAction(X),% extra indentation in the "loop body"
fail,
myPredicate(),
2004.11.3 AI程序设计 27
第 二 部分:第 9章 编码风格
9.3.7 Word格式化代码
? 这里将给我们一些提示, 使微软文字处理软件 Word的格式化代码变的更
容易 。
? 我们已经构造了一个代码段风格, 此外还有许多风格, 如:关键字
(keyword),参数 (parameter),变量 (var),文字 (literal)等各种风格 。
? 这个段落代码风格有下面的性质,
1) 它遵照了文本段落体的格式 。 这种格式在代码段下提供了合适的空格 。
2) 它避开了验证 ( 例如:拼写检查 ) 。
3) 它缩排一定数量, 并将默认的 TAB键大小设置为同样的数量 。 我们用
0.63cm,因为它是其它段落格式中的默认尺寸 。
? 它保持相同特性的行在一起, 以便程序部件不会跨页 。
返回
2004.11.3 AI程序设计 28
第 二 部分:第 9章 编码风格
9.3.7 Word格式化代码
?关键字的字符风格将字体变成 Arial,将字体版面样式变成粗体, 将
颜色变成暗黄 。 变量风格将变量颜色变成绿色, 文字风格将文字颜
色变成蓝色 。
?在打印代码的时候, 应首先选择代码段风格, 并使用软折行
(shift+newline)键入代码, 使用 TAB进行缩排 ( 在 tools ->
options … -> edit页面上重置 insert and backspace set left
indent选项 ) 。
? Timesaver( 节省时间 ),双击关键字之一并选择关键字字符的风
格 。 关键字一直选中的同时双击格式绘图笔 ( 工具栏中的画笔 ),
接着单击所有的关键字一次, 当所有的关键字被着色后, 按下 ESC
键或再次点击格式绘图笔 。
2004.11.3 AI程序设计 29
第 二 部分:第 9章 编码风格
9.4 程序设计语用学
当用 Visual Prolog 6编写程序时,Visual Prolog最好的实践包括
Prolog发展中心 (PDC)使用的格式建议与忠告。
该描述打算被用在新的 Visual Prolog代码中,而 PDC不希望将已
有的代码更新为下面这些标准。因此,在我们看到的 Visual Prolog发
行版本中可能没有下面这些标准。
注意,这些指南包含负面例子,例如:有毛病或有错误风格的例子
等。这些例子(或者负面部分)将以红色显示,有时甚至以粗体显示。
2004.11.3 AI程序设计 30
第 二 部分:第 9章 编码风格
9.4 程序设计语用学
9.4.1 常规技巧
9.4.2 布尔值
9.4.3 截断
9.4.4 红色截断和绿色截断
9.4.5 指派输入格式
9.4.6 异常和错误处理
9.4.7 内部错误和其它错误
2004.11.3 AI程序设计 31
第 二 部分:第 9章 编码风格
9.4.1 常规技巧
? 一个谓词的子句通常应该少于 20行,否则,我们应该考虑引入辅助谓词来处
理次要任务。有时一个谓词可能占据多于 20行。例如一事物有 50个属性必须
被设置,且每个设置需要一个调用就是一个例子。
? 使用完全合格的名称(例如,someClass::method),不管什么时候它
都使程序变得更清晰。
? 当与类名一起读时,类方法应是一个有意义的名称。在方法名中避免重复类
名,例如,这种名称可以是,
someClass::method
而不是
someClass::someClassMethod
? 一个谓词应该只做一件事。因此,如果一个谓词用来处理列表中的每一个元
素,那么考虑将它分成两个谓词,一个浏览这个列表,一个用来处理这些元
素。这样做的优点是使谓词变得更简单,因此更容易纠正,更容易理解。
2004.11.3 AI程序设计 32
第 二 部分:第 9章 编码风格
9.4.2 布尔值
? 如果某事件或真或假, 那么应该只用一个布尔变量 。 程序员不应该
用它去区分两个不同的事件, 例如左与右, 水平与垂直 。 在这些情
况下, 程序员应该用具体的论域来代替 。
domains
direction = left(); right()
orientation = horizontal(); vertical()
? 当一个逻辑变量以真条件命名, 此时一个变元表达了一个这样的事
实:如果事件为真就公布, 若为假就不公布, 我们称这样的变元为
可公布的 。
2004.11.3 AI程序设计 33
第 二 部分:第 9章 编码风格
9.4.3 截断
? 截断是一种谓词,它去掉了不确定性,例如:去掉了更进一步求解的
可能性(名称由此得来)。
? 它截断的这种不确定性可分为两组,虽然有些截断落在两组中,
1)截断是阻断回溯的可能性而转到当前谓词中紧接着的下一个子句。
2)截断是阻断对一个不确定性谓词调用的更进一步的解决方法。
? 除了上面所述的两种用法之外,截断再没有其他合理的用法。一旦理
解了这些目的,将截断放在一个正确的位置将是一件很容易的事。
1)或者将截断放在不再需要回溯后续子句的位置。
2)或者将它放在一个不确定性谓词的调用之后。对此,有一个惟一
的解决方案是非常重要的。
2004.11.3 AI程序设计 34
第 二 部分:第 9章 编码风格
9.4.3 截断
? 第一个目的可以由下面的例子解释,
clauses
p(17,X),-
X > 13,
!,
q(X),
,..,
p(A,X),-
,.,
? 在这个例子中, 程序中测试 X >13之后有一个截断 。 这是第一个合理使用截
断的一个非常典型的理由:, 子句实例对输入进行测试且在直接测试 X>13
之后, 即可找到正确的答案, 。
? 一般来说, 这样的一个截断典型情况下是放在子句头的后面, 要么放在紧挨
子句头的一个测试后面 。
2004.11.3 AI程序设计 35
第 二 部分:第 9章 编码风格
9.4.3 截断
? 第二个目的可以由下面的例子解释,
clauses
firstMember(X,L),-
member(X,L),
!,
? 在这个例子中, 截断被, 立即, 放在一个不确定性谓词的后面, 我们只对一
个解决方案有兴趣 。
? 上面我们两次突出了单词, 立即,, 这是因为在放置截断时这个关键字是
,立即, 的, 即截断应尽可能早地被放置在子句中 。
? 对于包含多于一个截断的子句, 我们应抱怀疑态度, 一个多于一个截断的子
句常常暗示了一个语法错误或一个设计错误 。
2004.11.3 AI程序设计 36
第 二 部分:第 9章 编码风格
9.4.4 红色截断和绿色截断
? 通常, 我们不鼓励将截断表示为绿色, 红色截断相当完
美 。
? 传统的 Prolog体系中已定义了红色截断和绿色截断的概
念 。 简单地说, 一个绿色截断出现时改变了谓词的语义,
而红色截断则没有 。
? 显然, 所有截断一个不确定性谓词的更进一步的解决方
法的截断自然是红色的 。 因此, 区分红色截断和绿色截
断只有一个目的, 即阻断回溯到下一个子句 。
2004.11.3 AI程序设计 37
第 二 部分:第 9章 编码风格
9.4.4 红色截断和绿色截断
? 考虑下面子句,
clauses
p(X),-
X > 0,
!,
,.,
p(X),-
X <= 0,
,.,
? 上面谓词中的截断是绿色的, 因为如果我们移动这个截断, 这段谓
词仍以同样的方式运行 。 如果这个截断出现在第一个子句中, 那么
第二个子句的测试 X <=0其实是不需要的,
2004.11.3 AI程序设计 38
第 二 部分:第 9章 编码风格
9.4.4 红色截断和绿色截断
clauses
p(X),-
X > 0,
!,
,.,
p(X),-
,.,
?然而如果没有这个测试, 这个截断将变成红色 。 因为现在如果我们移
动这个截断, 谓词将以不同的方式运行 。
?绿色截断可能似乎是多余的, 但是, 事实上它们被用做删除多余的回
溯点 ( 主要是考虑性能的原因 ) 。 在 Visual Prolog中, 绿色截断可
能也被用来使编译器信服某些谓词可能有特殊的模, 例如:
procedure。
2004.11.3 AI程序设计 39
第 二 部分:第 9章 编码风格
9.4.5 指派输入格式
? 用一个调度程序仅能处理简单的事情。用调度程序处理堵塞问题,
这一问题有一个非常单调的结构,特别是有许多不同的事件一定发
生。因此,逻辑上相关联的代码扩展了,且基本上没事可作的代码
块彼此紧挨着。
? 为了避免逻辑上相关联的代码被放在归类为一种模式的操作谓词中,
所以调度程序仅调用处理谓词。在这种方式里,调度程序仅仅是调
度程序,相关联的代码被靠近放置。
1) 如果一个谓词处理很多情况,那么保持每一种情况简单化。
2) 如果一个谓词也处理其它的案例,那么同一个案例中不要有很多
的子句。
第一个规则比较容易理解,第二个规则可由下面的例子加以解释,
2004.11.3 AI程序设计 40
第 二 部分:第 9章 编码风格
9.4.5 指派输入格式
clauses
qwerty(17,W,E,R,13,Y),-
,..,% A
!,
,.,% B
qwerty(17,W,E,R,13,Y),-
,..,% C
qwerty(Q,W,E,R,13,Y),-
,.,% D
..,
上面的子句表示了错误的代码格式, 因为它对同一个输入格式有两个子句 。 如
果这是谓词处理的惟一方式, 那么这样就行了 。 但是在这种情况下, 我们也有
其它形式的子句 。 我们认为上面的谓词应该被重写, 以便谓词子句 qwerty的目
的是为了规定输入的具体格式, 而将其它工作留给子谓词 。
2004.11.3 AI程序设计 41
第 二 部分:第 9章 编码风格
9.4.5 指派输入格式
clauses
qwerty(17,W,E,R,13,Y),-
!,% we have cased out,this is one of our cases
qwerty_17_w_e_r_13_y(W,E,R,Y),
qwerty(Q,W,E,R,13,Y),-
!,% we have cased out,this is one of our cases
qwerty_q_w_e_r_13_y(Q,W,E,R,Y),
,.,
clauses
qwerty_17_w_e_r_13_y(W,E,R,Y),-
,..,% A
!,
,.,% B
qwerty_17_w_e_r_13_y(W,E,R,Y),-
,.,% C
clauses
qwerty_q_w_e_r_13_y(Q,W,E,R,Y),-
,.,% D
2004.11.3 AI程序设计 42
第 二 部分:第 9章 编码风格
9.4.5 指派输入格式
?这些代码已经将 qwerty谓词变成将各种输入组合包装起来的谓词 。
像上面解释的那样, 对于每一种输入仅能有单个谓词调用, 这并不
是本质 。 主要的一点是不能以同样的输入方式从一个子句回溯到另
一个子句 。
?这个规则特别被用于事件管理者, 在同一事件处理中, 不应当用多
个子句去关闭或做其它事情 。 一个事件处理器经常分布在几页中,
因此, 如果它不具有一个案例被单一子句处理这样一个规则, 那么,
就必须去查看所有的子句以确保知道单个输入格式是怎样被处理的 。
2004.11.3 AI程序设计 43
第 二 部分:第 9章 编码风格
9.4.6 异常和错误处理
? 当一个异常或错误发生时引起一个 exception::raise异
常 。
? 如果想处理这个异常, 就必须先捕获这个异常 。
? 最终使用的时候, 如果我们想在异常情况下运行某些代
码, 那么继续这种异常 。
2004.11.3 AI程序设计 44
第 二 部分:第 9章 编码风格
9.4.7 内部错误和其它错误
? 我们应该区分内部错误, 以及与工具, 模型, 类, 单元等有关的错误 。 如果
某一内部不变量被破坏, 那么它是内部错误 。 典型的内部错误的例子是,
数据库中应定义的事实没有被定义 。
一个谓词应是一段程序, 但编译器并不认可通过加一个失败的子句构成的
谓词程序 。 如果那个子句是可达到的, 那么是基于了这样一个假设:即以前
的子句不能失败 ( 一个不变量 ) 是错误的 。
? 内部错误应该每单元分配一个异常, 我们应总是使用一个异常:一个用作用
户错误, 一个用作内部错误 ( 每单元用一个 ) 。 典型的用户错误有,
如果一个下标超出了限制
如果一个窗口操作是错误的 。
如果以错误的顺序调用谓词 。
返回
2004.11.3 AI程序设计 45
第 二 部分:第 9章 编码风格
9.4.7 内部错误和其它错误
? 以下是我们想要捕获出口的两个原因,
1) 因为某人想要处理异常, 比如说打开一个文件, 得到一个出口,
说明这个文件不存在 。 在这种情况下想捕获这个出口并显示一些错
误信息, 以说明这个文件不存在 。 当然如果这个异常不是某人想处
理的异常中的一个, 那么就必须继续这个异常 。
2) 因为我们不管谓词是否存在而想要做一些事情, 一个典型的例子
是:我们得到了一些做某事的锁, 并打开了这把锁 。 在这里我们想
要确保, 如果某事退出的话, 这把锁也被打开 。 因此我们最终使用
它 。
2004.11.3 AI程序设计 46
第 二 部分:第 9章 编码风格
9.5 存储管理
本节介绍在 Visual Prolog中是如何管理存储器的 。
从 Vip5到 Vip6,存储器处理做了彻底的改变,尤其是因
为现在的堆是依靠垃圾回收箱管理的 。
因而在本节中, 我们将学习运行堆栈, G-堆栈, 堆和垃
圾回收箱 。
2004.11.3 AI程序设计 47
第 二 部分:第 9章 编码风格
9.5 存储管理
9.5.1 存储器
9.5.2 运行堆栈
9.5.3 尾部调用优化
9.5.4 运行栈耗尽
9.5.5 全局栈
9.5.6 G-堆栈耗尽
9.5.7 堆和垃圾回收
9.5.8 垃圾回收
9.5.9 Finalizers
9.5.10 数据在什么地方分配
9.5.11 堆中数据
9.5.12 多线程和存储
2004.11.3 AI程序设计 48
第 二 部分:第 9章 编码风格
9.5.1 存储器
? Vip6程序使用三种存储数据的方法:运行堆栈,全局堆
栈,堆。
? 所有这些存储器都由各自的机制维持,并有各自的用途。
2004.11.3 AI程序设计 49
第 二 部分:第 9章 编码风格
9.5.2 运行堆栈
? 运行堆栈用于参数和局部变量 。 当引入谓词调用时, 其参数首先被
压到运行堆栈, 然后进行谓词调用 。 返回地址也传入运行堆栈 。
? 参数命令和返回地址的顺序都是依赖于调用约定的, 调用约定通过
使用, language”关键词进行声明 。
? 当输入谓词时, 它将在运行堆栈中为局部变量和一些数据分配空间 。
? 所有收集的参数, 返回地址, 局部变量等等, 统称为堆栈帧 。 当谓
词执行结束时, 堆栈帧将被移去 。
? 迄今为止, 这种处理与多数其他的程序设计语言中的处理一样, 但
是在处理不确定性谓词时区别较大 。
2004.11.3 AI程序设计 50
第 二 部分:第 9章 编码风格
9.5.2 运行堆栈
? 当谓词带激活的回溯点返回时, 如果使用了回溯点, 则需要堆栈帧 。
因此, 当谓词带激活的回溯点返回时, 堆栈帧将不再被移动 。
? 如果回溯点出现在嵌套调用中, 堆栈帧也将保存 。 因而一个回溯点
可以维持一个完整的堆栈帧, 链, 存活 。
? 如果回溯点被删除, 那么相应的堆栈帧链也将被移动 。 除非我们能
确认该链栈顶, 否则我们相信, 无论何时, 截断都能删除堆栈帧的
,顶部, 链 (当然也删除了回溯点 )。
? 运行堆栈经常被简称为栈,有时也称为堆 (尤其是在其他的语言里没
有回溯的概念, 因而当控制返回给调用者时, 不保留堆栈帧 )。
2004.11.3 AI程序设计 51
第 二 部分:第 9章 编码风格
9.5.3 尾部调用优化
? 习惯上尾部调用谓词被写成递归谓词, 也就是说, 谓词访问自身 。
例如, 表处理 。 每次谓词访问它本身时, 将创建一个新的堆栈帧,
如果这个表特别长, 那么相当数量的堆栈帧将被创建 。
? 由于堆栈帧有一个固定的尺寸 ( 一旦它被创建 ) 这对表的最大长度
作了个限制 。
? Vip6 与 Visual Prolog的以前版本一样, 执行一种最优化, 通常被
称作尾部调用优化 。 其思想是:在最后访问谓词后, 如果当前的堆
栈帧不需要它, 那么在调用尾部谓词前, 它将被删除 。
? 在这种思想的实际处理中有很多详细的资料, 但是重要的事情是尾
调用比其它调用使用更多的有效栈 。 然而, 如果在谓词中有一个回
溯点, 就需要堆栈帧的撤回 。
2004.11.3 AI程序设计 52
第 二 部分:第 9章 编码风格
9.5.4 运行栈耗尽
? 如果我们曾用完运行栈, 最大的可能性是在递归谓词中,
或在递归谓词的链上 。 在这里, 与尾调用一样递归不会
发生 。 或由于回溯点的原因, 必须保留堆栈结构 。
2004.11.3 AI程序设计 53
第 二 部分:第 9章 编码风格
9.5.5 全局栈
? 简单的数据如数字是围绕运行堆栈帧简单的被复制, 但是在列表被
作为参数或作为返回结果传递时, 拷贝长的列表将是非常昂贵的 。
因此像列表这样的大量数据将区别处理 。 大量数据既可以存储在所
谓的全局栈中 ( 常叫做 G-堆栈 ), 也可以存储在堆中 。 堆将在下面
进行讨论, 这里我们将集中讨论 G-堆栈 。
? 这样的大量数据被表示为数据本身的指针, 正如上面描述的指针在
堆栈帧被复制一样, 实际的数据一般存放在同一位置 。
? 一旦列表和其它算符值被创建, 它们将被存储在 G-堆栈中, 它们将
常驻在那里直到 G-堆栈被弹出 。
? 在任何需要的时候 G-堆栈将像堆一样增长, 当回溯时它们将被再次
弹出 。 其基本原理是, 如果谓词失败, 就不再需要数据了 。 我们这
里将不进一步详细描述这个机理 。
2004.11.3 AI程序设计 54
第 二 部分:第 9章 编码风格
9.5.6 G-堆栈耗尽
? 避免 G-堆栈耗尽的惟一方法就是回溯点。这看起来也许有点笨拙,
因为正常的回溯点是在不能找到问题的解时要做的事。
? 这可能确实是个问题:一种以此方式写出的程序,即一个一直仅仅
寻求预期路径的程序可能是因 G-堆栈耗尽而中止。
? GUI程序从事件处理器中获得一些帮助,因为这些程序总是因失败
而中止,并因此而释放 G-堆栈。每当事件处理结束,G-堆栈被重置
为处理程序开始时的大小。
2004.11.3 AI程序设计 55
第 二 部分:第 9章 编码风格
9.5.6 G-堆栈耗尽
? 我们应该了解一下典型的失败回路,
clauses
ppp(),-
generator (X),
action (X),
fail,
ppp(),
? 在执行之前和执行之后 (由于失败引起的回溯 )的 G-堆栈具有相同的大小 。
? 与 Vip5相比, Vip6在堆中存放比 G-堆栈中更多的数据, 因而 G-堆栈问题比
在 Vip5中要少发生 。
? Vip6只在 G-堆栈中放置算符数据,字符串和对象总是放置在堆中。
2004.11.3 AI程序设计 56
第 二 部分:第 9章 编码风格
9.5.7 堆和垃圾回收
? 堆被用于数据, 这些数据必须使回溯存在 。 而对于那些不能被移动
的数据 ( 有关更多的内容在下面叙述 ), 基本上指的是事实库和对
象 。 但是为了澄清下面的描述, 其他数据也可以存储在堆中 。
? 堆通过垃圾回收箱来管理 。 程序员从堆中分配存储器 ( 隐含地而不
是明确地 ), 但是它将不再释放存储器 ( 隐含地或明确地 ) 。 取而
代之的垃圾回收箱, 它不断地分析堆并且释放不再需要的存储器,
即它收集垃圾 。
? 这是管理堆的原则 。 当需要堆存储器的时候, 可能发生下面三种情
况之一,
垃圾回收箱有一个是可利用的适当的堆存储器段, 该段被返回 。
从操作系统中分配新的内存 。
堆是回收的垃圾, 而分配是重新尝试 。
2004.11.3 AI程序设计 57
第 二 部分:第 9章 编码风格
9.5.8 垃圾回收
? Visual Prolog 6使用传统的 Boehm-Demers-Weiser垃圾回收箱 。
? 让我们考虑一个程序, 这段程序只分配存储器, 从不对其重新分配 。
? 在某一时间程序要做一些分配, 但是, 不是所有这些分配都能在程
序中达到 。 程序只能存取存储器的直接指针或间接指针 。 但是当程
序退出子程序时, 指针就无效 。 子程序中的变量和参数也不再存在 。
? 程序能达到的存储器和数据被称作活数据, 其余的是死存储器或垃
圾 。
? 垃圾回收箱的目的是到处查找垃圾并使它能再使用 (为活数据 )。
2004.11.3 AI程序设计 58
第 二 部分:第 9章 编码风格
9.5.8 垃圾回收
? 为了查找垃圾, 垃圾回收箱负责查找和标记全部的活数据, 在此过
程中, 所有没被标记的存储器分配都是垃圾 。
? 为了标记活的数据, 垃圾回收箱从所谓的根系集合 ( root set) 开
始, 它是全局变量加上现有的局部变量和参数 。 所有从根系集合指
向的存储器被标记, 否则所有从标记的存储器指向的存储器被标记 。
当不再有存储器可以用这种方式被发现时, 所有的活数据即被标记,
而所有没被标记的存储器是垃圾 。
? 在垃圾回收箱回收垃圾之前, 它为垃圾运行 finalizers。 finalizers
是一个可选的用户自定义的例程, 这个例程仅在一段存储器被收回
前执行 。 运行相应的 finalizers之后, 垃圾被回收, 这样存储器就能
再使用 。
2004.11.3 AI程序设计 59
第 二 部分:第 9章 编码风格
9.5.9 Finalizers
? Visual Prolog 6在对象上支持 finalizers:为了拥有 finalizers,在
一个对象构造类中简单地用谓词 finalizers/0编写子句 。 该谓词被隐
含定义, 而不能显式定义 。
? 注意:以, waves”形式回收存储器是垃圾回收算法的自然特性, 在
一段时间, 有可能没有回收什么, 有可能回收了很多, 所以
finalizers以, waves”的形式运行 。
? 应该记住, 当存储器处于非活动状态时, finalizers不是 (必要地 )立
即执行 。 在垃圾回收期间, 当垃圾回收箱回收存储器时, 执行才开
始发生 。
2004.11.3 AI程序设计 60
第 二 部分:第 9章 编码风格
9.5.10 数据在什么地方分配
? 上面我们提到运行堆栈保存参数和局部变量, 但那只是部分正确的 。
实际上运行堆栈只保存数字, 字符和指针, 任何非数字或字符的东
西被表示为实际数据的指针 。 实际的数据可存储在 G-堆栈或堆中 。
? 同样也提到, G-堆栈在回溯点上被弹出, 因而任何激活回溯的数据
必须存储在堆中 。 当数据被声明为事实时, 它必须激活回溯, 并且
该数据被从 G-堆栈复制到堆 (如果它是新近被存储在 G-堆栈 )。
? 数据从不以别的方式被复制, 数据一旦在堆中, 那么它将一直存在
于堆中, 直到它被垃圾回收站所回收 。
2004.11.3 AI程序设计 61
第 二 部分:第 9章 编码风格
9.5.10 数据在什么地方分配
? 确实没必要拥有一个 G-堆栈, 在 G-堆栈中被分配的空间同样可在堆
中被分配 。 但 G-堆栈比堆有一些性能上的优势:分配和去分配相当
快, 因为它只是改变一个指针 。 换句话说, 无论数据是否是真活着,
分配在 G-堆栈中的数据必须停留在那里, 直到回溯时释放它 。 这样 G
-堆栈将常常包含一些不可回收的垃圾 。
? 因为 G-堆栈既有优点又有缺点, 我们对什么样的数据放在 G-堆栈没
有约定 。 实际上, 我们甚至不能保证 G-堆栈将在未来继续存在 。
2004.11.3 AI程序设计 62
第 二 部分:第 9章 编码风格
9.5.11 堆中数据
? 对象始终存储在堆中, 主要原因是对象不能被复制, 亦即不能从 G-
堆栈到堆进行复制 。 对象不仅是一个值, 它还携带有变化的状态,
如果对象被复制, 将存在对象的两个版本, 一个对象的改变不会在
另一个对象中被观察到 。 因而从一开始, 对象就被存储在堆中 。
? 字符串是被存储在称为原子堆的堆的特殊部分 。 堆的这部分是非常
有效的 。 但是它只能被用于不包括任何指针的数据 。 之所以有效是
因为它在垃圾回收期间从来不扫描指针 。 当我们已经知道数据不包
含指针时, 就没有必要去扫描它们 。
? 同样提到, 存储在事实中的数据也存储在堆中 。
2004.11.3 AI程序设计 63
第 二 部分:第 9章 编码风格
9.5.12 多线程和存储
? 多线程程序中的每个线程执行独立于其他线程的谓词调用和回溯 。 因此每个
线程有它自己的堆和 G-堆栈 。 换句话说, 只有一个堆被所有的线程所共享 。
? 只有一个线程能存取运行堆栈和 G-堆栈, 因此即使没有更进一步的同步机制,
它也能被存取 。 换句话说, 在堆中的数据能被所有的线程存取 。 Visual
Prolog不强迫存储机制有任何的同步 。 程序员必须保证以尽可能明智的方
式存取数据 。
? 除了事实库 ( 包括对象中的那些 ) 之外, Visual Prolog中的数据是不可变
的 。 不可变数据没有任何的同步问题, 很多线程可以同时读取那些数据 。
? 当两个 ( 或更多 ) 线程同时更新同一段数据时将出现问题 。 这样, 如果两个
或更多线程同时从同样的事实库声明或撤消事实时, 可能会遇到问题 。
返回
2004.11.3 AI程序设计 64
第 二 部分:第 9章 编码风格
9.5.12 多线程和存储
? 声明和撤消例程的执行可以确保事实库有一个好的结构, 从某种意义上说,
事实数据库的后续使用将不会引起任何的违规存取 。
? 但是, 如果两个或更多的线程同时更新事实数据库, 一些更新可能会被丢失 。
举例来说, 如果两个事实被同时声明, 这可能造成一个事实被插入数据库中,
另一个事实被丢失 。
? 在读取事实库的同时更新它是合理的 。
? PFC提供了同步方法, 可将它用于同步事实更新 。
2004.11.3 AI程序设计 65
第 二 部分:第 9章 编码风格
9.6 异常处理
所谓异常是指一段程序背离其正常的执行路径 。 PFC的异常包, 包
含了捕获异常的谓词, 它们会出现在 prolog程序中 。
这一节叙述如下内容,
1) 如何处理异常;
2) 如何构造自己的异常;
3) 怎样继续另一个异常 。
本节讨论的范例程序是 exception.zip,可以在网上下载 。
同时还可以参见 Visual Prolog的在线帮助里, Prolog基类 /异常,
一节中关于异常的详细描述 。
2004.11.3 AI程序设计 66
第 二 部分:第 9章 编码风格
9.6 异常处理
9.6.1 如何捕获异常
9.6.2 如何构造自己的异常
9.6.3 如何继续另一个异常
2004.11.3 AI程序设计 67
第 二 部分:第 9章 编码风格
9.6.1 如何捕获异常
? 首先考虑这样一段程序, 程序的任务是读取一个文本文件并把其内容打印到
控制台 。 PFC提供了 file::readString谓词, 它能完成上述任务 。 但是, 有
一些情况可能妨碍实现该任务;例如, 一个指定的文件名可能指向一个根本
不存在的文件 。 当谓词 file::readString无法完成任务时, 它就产生一个异
常 。
? Visual Prolog提供了一个内置的谓词 trap/3来捕获异常并对此进行处理 。
关于谓词 trap/3的更详细的内容, 可参见 Visual Prolog的在线帮助中的相
关主题 (, LanuageReference -> Built-in Domains,Predicates and
Constants-> Predicates ->trap”主题 ) 。
2004.11.3 AI程序设计 68
第 二 部分:第 9章 编码风格
9.6.1 如何捕获异常
? 捕获一个异常的代码为,
trap(MyTxtFileContent = file::readString(myFileName,_IsUnicode),
ErrorCode,handleFileReadError(ErrorCode)),
? 最有趣的部分是谓词 handleFileReadError的实现,
? 当预期的 fileSystem_api::cannotcreate异常出现的时候, 具有捕获和
处理这一异常参数的异常处理谓词 exception::tryGetDescriptor恰好正
确地被调用 。 例子当中, 我们使用控制台的输出来给出异常原因的解释,
stdIO::write("Cannot load file due to,",ExceptionDescription,
"\nFileName,",FileName,
"\nReason,",Reason ),
? 完整的例子程序可参见项目 catchException\catchException.prj6。
2004.11.3 AI程序设计 69
第 二 部分:第 9章 编码风格
9.6.2 如何构造自己的异常
? 让我们考虑一个检查特定文
件的长度或大小的程序 。
PFC 提供了一个谓词
file::getFileProperties 来
返回一个特定文件的长度 。
如果文件长度为 0,我们可以
通过谓词 exception::raise
来构造一个异常 。 同时有必
要创建一个谓词, 以此指定
该异常 。 为此, 我们已经创
建了
myExceptionZeroFileSize。
构造一个异常的代码如下,
clauses
run():-
console::init(),
trap(file::getFileProperties(myFileName,
_Attributes,Size,_Creation,
_LastAccess,_LastChange),
ErrorCode,
handleFileGetPropertiesError(ErrorCode)),
Size = unsigned64(0,0),% file size is zero
!,
exception::raise(classInfo,
myExceptionZeroFileSize,
[namedValue(fileSystem_api::fileName_par
ameter,
string(myFileName))]),
run(),
2004.11.3 AI程序设计 70
第 二 部分:第 9章 编码风格
9.6.2 如何构造自己的异常
? 谓词 exception::raise的第一个参量是 classInfo谓词, 它是由 VDE为任何
新创建的类而生成的 。 当然, 在一个异常产生时, 指明额外的信息是一个好
的方法 。 在我们的例子中, 文件的名字非常有用, 因为零长度归属于所指定
的文件 。
? 谓词 myExceptionZeroFileSize用以下模板生成,
clauses
myExceptionZeroFileSize(
classInfo,
predicate_Name(),
"File size cannot be zero"),
? 这就是说, 异常谓词的第一个参数和第二个参数通常分别是 classInfo和
predicate_Name(),而第三个参数包含该异常原因的一个文本解释 。
? 完整的例子程序可参见项目 raiseException\raiseException.prj6。
2004.11.3 AI程序设计 71
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常
? 我们考虑一个 DLL程序, 该程序读取一个文本文件, 然后用一个参数返
回其内容 。 如果异常出现了, 那么 DLL继续, 同时补充一个关于 DLL版
本的额外信息 。 继续该异常的代码如下,
constants
dllVersion = "1.20.0.0",
clauses
loadFile(FileName) = FileContent,-
trap(FileContent = file::readString(FileName,_IsUnicode),
ErrorCode,
exception::continue(ErrorCode,
classInfo,continuedFromDll,
[namedValue(version_Parameter,string(dllVersion))])),
2004.11.3 AI程序设计 72
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常
? 谓词 continuedFromDll声明如下,
predicates
continuedFromDll, core::exception as
"_ContinuedFromDll@12"
? 它从该 DLL中输出 。 谓词 continuedFromDll的实现由下面的模板产生
( 与前面的 myExceptionZeroFileSize相同 ),
clauses
continuedFromDll(
classInfo,
predicate_Name(),
"Exception continued from continueException.DLL"),
? 当谓词 exception::continue继续该异常的时候, 会增加一个关于 DLL
版本 ( dllVersion) 的额外信息 。
? 完整的例子可参见项目 continueException\continueException.prj6。
2004.11.3 AI程序设计 73
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常
? 让我们看看如何捕获这样一个继续的异常 。 其代码与上面讨论的
代码类似 。 在这里, 我们只集中考查二者不同的方面 。
constants
myFileName = "my.txt",
clauses
run():-
console::init(),
initExceptionState(exception::getExceptionState()),
trap(MyTxtFileContent = loadFile(myFileName),
ErrorCode,handleFileReadError(ErrorCode)),
!,
stdIO::write("The content of ",myFileName,"is:\n",MyT
xtFileContent),
run(),
2004.11.3 AI程序设计 74
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常 class predicates handleFileReadError, ( exception::traceID ErrorCode )failure,
clauses
handleFileReadError(ErrorCode):-
DescriptorContinued = exception::tryGetDescriptor(ErrorCode,continuedFromD
ll),
Descriptor = exception::tryGetDescriptor(ErrorCode,fileSystem_api::cannotcre
ate),
!,% file cannot be loaded
exception::descriptor(_ErrorCodeContinued,
_ClassInfoContinued,
_ExceptionContinued,
_KindContinued,
ExtraInfoContinued,
_CursorPositionContinued,
_GMTTimeContinued,
ExceptionDescriptionContinued) = DescriptorContinued,
Version = core::mapLookUp(ExtraInfoContinued,
version_Parameter,string("")),
stdIO::write("Exception continued, ",ExceptionDescriptionContinued,
"\nDllVersion,",Version,"\n"),
2004.11.3 AI程序设计 75
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常
exception::descriptor(_ErrorCode,
_ClassInfo,
_Exception,
_Kind,
ExtraInfo,
_CursorPosition,
_GMTTime,
ExceptionDescription) = Descriptor,
FileName = core::mapLookUp(ExtraInfo,
fileSystem_api::fileName_parameter,string("")),
Reason = core::mapLookUp(ExtraInfo,
common_exception::errorDescription_parameter,string("")),
stdIO::write("Cannot load file due to,",ExceptionDescription,
"\nFileName,",FileName,
"\nReason,",Reason ),
exception::clear(ErrorCode),
fail,
handleFileReadError(ErrorCode):-
isDebugMode = true,
!,
exceptionDump::dumpToStdOutput(ErrorCode),
exception::clearAll(),
fail,
handleFileReadError(ErrorCode):-
exception::clear(ErrorCode),
fail,
2004.11.3 AI程序设计 76
第 二 部分:第 9章 编码风格
9.6.3 如何继续另一个异常
? 在该代码中,我们想要找到 continuedFromDll和
fileSystem_api这两个异常,以及相关联的处理它们的信息。这
清楚表明,如果一个异常延续的话,我们就可以得到关于该异常的
更多的信息。
? 完整的例子程序可参见项目
continueException\testContinuedException
\testContinuedException.prj6。在运行可执行程序
testContunedException之前,必须要首先建立项目
continueException\continueException.prj6。
2004.11.3 AI程序设计 77
第 二 部分:第 9章 编码风格
本章小结
本章介绍了 Visual Prolog 6的编码风格, 内容包
括基本元素, 推荐格式, 程序结构, 程序设计语用学,
存储管理, 以及异常处理 。 这里描述的 Visual Prolog
程序的编码标准, 是 Visual Prolog系统本身的一部分 。
用户文档中的例子也是标准的, 它们同样也代表了
Prolog发展中心为用户推荐的编码标准 。
2004.11.3 AI程序设计 78
第 二 部分:第 9章 编码风格
习 题
1,Visual Prolog程序的编码标准指的是什么? Visual Prolog程序
的编码标准中, 关键内容有哪些?
2,Visual Prolog程序代码的格式指哪些方面?
3,Visual Prolog程序设计语用学中, 有哪些常规技巧?
4,试分析, 截断, 在 Visual Prolog程序设计中的作用 。
5,指派输入格式的目的是什么?
6,测试位于 continueException\testContinuedException目录下
的完整的例子程序 testContinuedException.prj6。