2004.11.3 AI程序设计 1
第 二 部分:第 5章 Prolog基础
第 5章 Prolog基础
? 本章介绍 学习关于 Prolog编程最基本的内容, 包括 Horn子句, Prolog推理
机, 程序控制, Prolog算符等 。
? Visual Prolog是面向对象的, 严格类型化的和模式检验的 。 在编写 Visual
Prolog程序时, 必须掌握这些内容 。 但在这里, 我们将集中于编写代码这
个核心问题, 也就是说, 编写这些代码时暂时不考虑类, 类型和模式 。
? 我们将使用包含在 Visual Prolog 6中的 PIE例子 。 PIE是一个经典的 Prolog
解释器, 通过使用它, 可以学会和试验 Prolog程序, 而不必关心类, 类型
等方面的知识 。
? 内容是基于使用 Build 6004或以后的 Visual Prolog 6版本, 否则, PIE应
用程序将不会像我们描述的这样工作 。 这个编译号可以在 VDE的 About对话
框中找到 。
2004.11.3 AI程序设计 2
第 二 部分:第 5章 Prolog基础
第 5章 Prolog基础
5.1 Horn子句逻辑
5.2 Prolog推理机
5.3 扩展家庭定理
5.4 Prolog是一种编程语言
5.5 程序控制
5.6 Prolog算符
本章小结与习题
2004.11.3 AI程序设计 3
第 二 部分:第 5章 Prolog基础
5.1 Horn子句逻辑
? Visual Prolog和其他用语都是基于 Horn子句逻辑的 。 Horn子句逻
辑是对事物及其相互关系进行推理的形式系统 。
? 在自然语言中, 我们可以这样表达一个陈述句,
John是 Bill 的父亲,
? 这里我们涉及两个实体, John和 Bill,以及他们之间的关系, 即一
个是另一个的父亲 。 在 Horn子句逻辑中, 可以这样形式化地表述上
面的陈述句,
father(“Bill”,“John”),
? 上面的 father是带有两个参量的一个谓词或关系, 它表示第二个人
是第一个人的父亲 。
2004.11.3 AI程序设计 4
第 二 部分:第 5章 Prolog基础
5.1 Horn子句逻辑
? 像, John是 Bill 的父亲, 这样的陈述称为事实, 而, X是 Z的祖父,
如果 X是 Y的父亲且 Y是 Z的父亲, 称为规则 。
? 我们可以用事实和规则来形成定理, 一个定理是事实和规则的集合,
让我们陈述一个小定理,
father("Bill","John"),
father("Pam","Bill"),
grandFather(Person,GrandFather),-
father(Person,Father),
father(Father,GrandFather),
2004.11.3 AI程序设计 5
第 二 部分:第 5章 Prolog基础
5.1 Horn子句逻辑
? 这个定理的作用是回答这样一些问题,
?John是 Sue的父亲吗?
?谁是 Pam的父亲?
?John是 Pam的祖父吗?
?..,
? 这些问题称为目标 ( goal) 。 它们可以这样形式化表述,
??- father("Sue","John"),
??- father("Pam",X),
??- grandFather("Pam","John"),
2004.11.3 AI程序设计 6
第 二 部分:第 5章 Prolog基础
5.1 Horn子句逻辑
? 这些问题被称为目标子句 ( goal clause) 或简称为目标 ( goal) 。
事实 ( facts), 规则 ( rules) 及目标 ( goals) 合起来称为 Horn
子句, 因此得名 Horn子句逻辑 。
? 一个 Prolog程序是一个定理和目标的集合 。 当程序开始时, 它试图
使用定理, 为目标找到一个解 。
2004.11.3 AI程序设计 7
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? PIE (Prolog Inference Engine),即 Prolog推理机,随 Visual
Prolog 6一起提供。
? 在开始之前,必须先安装和建立 PIE
1) 在 Windows开始菜单中选择, 安装例子,, ( Start -> Visual
Prolog 6 -> Install Examples) 。
2) 用 VDE打开 PIE项目, 且运行该程序 。
2004.11.3 AI程序设计 8
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? 当程序启动后,其情形如下图 5.1所示。
图 5.1 Prolog
推理机
2004.11.3 AI程序设计 9
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? 选择菜单 File -> New,键入前面的父亲子句和祖父子句,如图 5.2
所示。
图 5.2 子句代码
2004.11.3 AI程序设计 10
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? 当编辑器窗口激活时,选择 Engine -> Reconsult,将会把文件装
入到推理机。在对话框中,还将得到这样一个消息,
Reconsulted from,....\pie\Exe\FILE4.PRO
? 无论用编辑器如何装入,其内容都不会保存到文件之中。如果想要
保存内容,必须使用菜单命令 File -> Save。
? 菜单 File -> Consult不管文件是否因编辑而打开,都会装载磁盘文
件中的内容。
? 一旦查阅过定理,就可以回答各种目标。
2004.11.3 AI程序设计 11
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? 在对话框窗口的空白行上,键入一个目标,不带前缀,?-”。例如,
键入如图 5.3所示的查询代码。
图 5.3 键入目标
2004.11.3 AI程序设计 12
第 二 部分:第 5章 Prolog基础
5.2 Prolog推理机
? 当插字符号位于行尾时,按下键盘上的输入键。 PIE现在将把从该行
的开头到插字符号之间的文本当作目标来执行。于是,就可以看到
如图 5.4所示的结果。
图 5.4 查询目标对话框
2004.11.3 AI程序设计 13
第 二 部分:第 5章 Prolog基础
5.3 扩展家庭定理
? 使用诸如 mother和 grandMother这样的谓词,可以直接扩展家庭定理。读
者应该试着亲自去做,也可以试着添加上更多的人。我们建议读者使用自己
家庭中的人,因为这样易于验证,且可以不考虑添加的这个人是否真正是自
己的祖母。
? 给出谓词 mother和 father,我们还可以定义双亲( parent)这个谓词。一
位母亲是双亲,一位父亲也是双亲,因此我们可以使用两个子句来定义双亲,
parent(Person,Parent),- mother(Person,Parent),
parent(Person,Parent),- father(Person,Parent),
第一个规则可以解读为:如果 Parent是 Person的 mother,则 Parent是
Person的双亲( parent)。
还可以用分号, ;, 来定义双亲 ( parent) 关系, 分号代表, 或 ( or), 。
2004.11.3 AI程序设计 14
第 二 部分:第 5章 Prolog基础
5.3 扩展家庭定理
parent(Person,Parent),-
mother(Person,Parent);
father(Person,Parent),
? 这条规则可以解读为:如果 Parent是 Person的 mother或( or) Parent是
Person的 father,则 Parent是 Person的双亲( parent)。
? 我们强烈建议读者尽可能少用或根本不用这个分号“;”。之所以这样建议,
是基于以下理由,
1) 逗号,,, 和分号, ;, 之间在印刷上的差别非常小, 但语义上的差别却很
大 。 分号, ;, 常常是引起混淆的一个根源, 因为它容易被误解为逗号
“,,, 特别是当它处于一个长行的末尾时 。
2) Visual Prolog只允许在最外一层使用分号( PIE允许任意层次的嵌套)。
2004.11.3 AI程序设计 15
第 二 部分:第 5章 Prolog基础
5.4 Prolog是一种编程语言
? Prolog可以作为专家系统来使用,但它本身却是作为一种程序语
言而设计出来的。
? 我们遗漏了把 Horn子句逻辑变为一种程序设计语言的两个重要因
素,
1)严格的搜索顺序或程序控制
2)副效应
2004.11.3 AI程序设计 16
第 二 部分:第 5章 Prolog基础
5.5 程序控制
? 当试图为如下目标寻找一个解时,
- father(X,Y),
? 可以有许多实现方式 。 例如, 如果只考虑定理中的第二个事实, 那么,
就可得到一个解 。
? 但是 Prolog不使用随机搜索策略, 而总是使用同一种策略 。 系统保持
一个当前目标, 始终从左到右进行求解 。
例如, 如果当前目标是,
- grandFather(X,Y),mother(Y,Z)
那么, 系统就会尝试在求解子目标 mother(Y,Z)之前, 首先求解子目
标 grandFather(X,Y )。 如果第一个 ( 即最左面的 ) 子目标不能被求
解, 则全部问题就没有解, 第二个子目标根本就不用再尝试求解 。
2004.11.3 AI程序设计 17
第 二 部分:第 5章 Prolog基础
5.5 程序控制
?当求解一个特定子目标时, 事实和规则将被自上而下进行尝试 。
?当使用规则求解了一个子目标时, 当前目标中待求解的那个子目标将
被其规则右边的那些子目标所代替 。
?例如, 如果当前目标是,
- grandFather(X,Y),mother(Y,Z),
?而我们使用规则
grandFather(Person,GrandFather),-
father(Person,Father),father(Father,GrandFather),
?去求解第一个子目标, 则作为结果的当前目标是
- father(X,Father),father(Father,Y),mother(Y,Z),
2004.11.3 AI程序设计 18
第 二 部分:第 5章 Prolog基础
5.5 程序控制
?注意,规则中的某些变量已经被子目标中的变量替换,我们将在后面详细解
释这一点。
?给定这种评价策略,就可以更多地从程序上解释子句的控制过程。
?考虑这个规则,
grandFather(Person,GrandFather),-
father(Person,Father),father(Father,GrandFather),
?给定严格的评价策略,我们可以这样解读这个规则:为了求解
grandFather(Person,GrandFather),首先要求解 father(Person,
Father),然后再求解 father(Father,GrandFather)。或者这样理解:当调
用 grandFather(Person,GrandFather) 时,首先要调用 father(Person,
Father),然后再调用 father(Father,GrandFather)。
?有了这种程序上的解读,我们就可以理解,所谓的谓词实际上就相当于其他
编程语言中的过程或子例程。它们之间主要的区别在于一个 Prolog谓词对于
一个单个提问可以返回多个结果或没有结果(即失败)。这一点将在下一节
详细进行讨论。
2004.11.3 AI程序设计 19
第 二 部分:第 5章 Prolog基础
5.5.1 失败
? 在定理中, 一个谓词提问可能没有任何一个解 。 例如调用
parent("Hans",X),因为不存在适用于 Hans的双亲关系的事
实或规则, 所以没有一个解 。 我们就说这个谓词调用失败
( fail) 。 如果目标失败了, 则说明定理中完全不存在针对该目
标的解 。 下一节将解释在通常情况下, 即当它不是一个失败的目
标时, 如何处理失败 。
2004.11.3 AI程序设计 20
第 二 部分:第 5章 Prolog基础
5.5.2 回溯
? 在 Prolog程序的过程性解释中,, 或 ( or), 以相当特殊的方式
进行处理 。 考虑下面的子句,
parent(Person,Parent),-
mother(Person,Parent);
father(Person,Parent),
? 从逻辑上解读, 我们这样解释这个子句:如果 Parent是 Person
的母亲, 或 ( or ) Parent 是 Person的父亲, 则 Parent 是
Person的双亲 。
2004.11.3 AI程序设计 21
第 二 部分:第 5章 Prolog基础
5.5.2 回溯
? 对于 Parent谓词的提问,, 或 ( or), 引出了两个可能的解 。 Prolog处
理这种多项选择时, 往往首先尝试第一个选择, 随后根据需要, 再回溯
到下一个备份的选择, 等等 。
? 在程序执行期间, 来自早期谓词调用的许多备份的选择 ( 称为回溯点 )
可能继续存在 。 如果有的谓词调用失败了, 那我们将回到上一个回溯点
尝试另一个选择性的解 。 如果没有回溯点存在了, 则整个目标就失败了,
也意味着没有一个解存在 。
? 这样,我们就可以这样解释上面的子句:当 parent(Person,Parent)被
调用时,首先给第二个选择性的解(即对于调用 father(Person,
Parent))记录一个回溯点,接着再调用 mother(Person,Parent)。
2004.11.3 AI程序设计 22
第 二 部分:第 5章 Prolog基础
5.5.2 回溯
? 例 为阐明程序如何执行, 我们来详细考察一个例子 。 考虑下面这些子
句,
mother("Bill","Lisa"),
father("Bill","John"),
father("Pam","Bill"),
father("Jack","Bill"),
parent(Person,Parent),-
mother(Person,Parent);
father(Person,Parent),
? 然后考虑目标,
- father(AA,BB),parent(BB,CC),
2004.11.3 AI程序设计 23
第 二 部分:第 5章 Prolog基础
5.5.3 改进家庭定理
? 如果继续处理上述家庭关系, 就可能会发现难于处理诸如兄弟,
姐妹这样的关系, 这是因为要确定一个人的性别是相当困难的,
除非这个人是一位父亲或母亲 。
? 问题是我们选择了一个不好的方式来形式化这个定理 。 原因在于,
我们从考虑实体间的关系开始 。 如果我们首先考虑实体本身, 那
结果就会不同 。
? 我们的主要实体是人, 人都有名字 ( 在这个简单的例子中, 我们
仍然用名字来标识人 。 而在一个真正有规模的程序中, 这一点未
必成立 。 ) 。 人有性别 。 人还有许多其他的特性, 但与我们的兴
趣无关, 所以没有出现在我们的上下文中 。
2004.11.3 AI程序设计 24
第 二 部分:第 5章 Prolog基础
5.5.3 改进家庭定理
? 由此我们这样定义一个 person谓词,
person("Bill","male"),
person("John","male"),
person("Pam","female"),
? person谓词的第一个参数是名字, 第二个参数是性别 。
? 我们不是使用 mother和 father作为事实, 而是选择 parent作为
事实, 将 mother和 father作为规则,
parent("Bill","John"),
parent("Pam","Bill"),
father(Person,Father),- parent(Person,Father),person(Father,
"male"),
? 注意, 当 father是一个, 导出, 关系时, 不可能有女性的父亲 。
所以这个定理在内部要保持一致, 在这一点上不能有其他的形式 。
2004.11.3 AI程序设计 25
第 二 部分:第 5章 Prolog基础
5.5.4 递归
? 利用上面给出的原则, 大多数家庭关系是容易建立的 。 但当它涉
及到像祖先这样的, 无穷, 关系时, 我们将需要更多的规则 。 如
果我们遵循上述原则, 就应该这样定义祖先,
ancestor(Person,Ancestor),- parent(Person,Ancestor),
ancestor(Person,Ancestor),- parent(Person,P1),parent(P1,
Ancestor),
ancestor(Person,Ancestor),- parent(Person,P1),parent(P1,P2),
parent(P2,Ancestor),
..,
? 主要问题是子句的排列永远不会完结 。 克服这一问题的方法是使
用一个递归定义, 即像下面这样, 根据它自身进行的定义,
2004.11.3 AI程序设计 26
第 二 部分:第 5章 Prolog基础
5.5.4 递归
ancestor(Person,Ancestor),- parent(Person,Ancestor),
ancestor(Person,Ancestor),- parent(Person,P1),
ancestor(P1,Ancestor),
? 这一声明是说, 双亲是一个祖先, 双亲的祖先还是祖先 。
? 如果还没有熟悉这种递归的概念, 就可能会觉得递归很难处理 。 递归是
Prolog 程序的基础, 需要反复加以使用, 然后才会习惯 。
? 让我们试着执行一个祖先 ancestor目标,
- ancestor("Pam",AA),
? 我们为第二个 ancestor子句设置一个回溯点,然后执行第一个子句。这
时,新的目标为,
- parent("Pam",AA),
2004.11.3 AI程序设计 27
第 二 部分:第 5章 Prolog基础
5.5.4 递归
? 这样很快找到一个解,
AA="Bill",
? 然后, 我们试图通过使用第二个 ancestor子句的回溯点来找寻另一个解 。
这时, 新的目标为,
- parent("Pam",P1),ancestor(P1,AA),
? 由于 "Bill"是 "Pam"的双亲, 所以我们找到 P1= "Bill"。 接着, 目标为,
- ancestor("Bill",AA),
? 为了求解这个目标,我们首先为第二个 ancestor子句设置一个回溯点,然
后执行第一个子句。这时给出下列目标,
- parent("Bill",AA),
? 这个目标又给出一个解,
AA = "John",
? 所以我们找到 "Pam"的两个祖先,"Bill"和 "John"。
2004.11.3 AI程序设计 28
第 二 部分:第 5章 Prolog基础
5.5.4 递归
? 如果我们使用第二个 ancestor子句的回溯点, 将得到下列目标,
- parent("Bill",P1),ancestor(P1,AA),
? 在这里, 我们将再一次发现 "John"是 "Bill"的双亲, 因此 P1为
"John"。 这又给出了目标,
- ancestor("John",AA),
? 如果我们继续求解这个目标,将发现这个目标没有任何一个解存在。
所以,归根到底我们只可能找到 "Pam"的两个祖先。
2004.11.3 AI程序设计 29
第 二 部分:第 5章 Prolog基础
5.5.4 递归
?递归是一种非常强大的功能, 但它也有一点难于控制 。 使用递归时,
切记以下两个要点,
递归必须能够前进;
递归必须能够终止 。
?在上面的代码中, 第一个子句确保该递归能够终止, 这是因为这个
子句不是递归的, 即它没有对该谓词本身进行调用 。 第二个子句是递
归的 。 在第二个子句中, 我们保证在进行递归调用之前, 更深一层地
回退一个祖先步 。 也就是说, 我们确保在问题求解过程中将不断取得
一些进展 。
2004.11.3 AI程序设计 30
第 二 部分:第 5章 Prolog基础
5.5.5 副效应
?除了严谨的计算顺序以外, Prolog也有一些副效应 。 例如, Prolog
有一些用于读, 写操作的预定义谓词 。
?下面的目标将输出找到的 "Pam"的祖先,
- ancestor("Pam",AA),write("Ancestor of Pam, ",AA),nl(),
?ancestor调用将找到 "Pam"的一个祖先, 并放在 AA中 。
?write调用将输出字符串 "Ancestor of Pam, ", 然后输出 AA的值 。
?nl调用将会使输出转入到一个新行 。
?当在 PIE中运行程序时, PIE自己会给出结果 。 所以会造成你的结果
和 PIE的结果混在一起, 所得出的结果可能并不是想要的 。
2004.11.3 AI程序设计 31
第 二 部分:第 5章 Prolog基础
5.5.5 副效应
?避免 PIE本身输出结果的一个非常简单的方法就是确保该目标没有解 。
考虑下面的目标,
- ancestor("Pam",AA),write("Ancestor of Pam, ",AA),nl(),fail,
?fail是一个预先定义的谓词, 它总是失败, 即不会有解 。
?前三个谓词调用和上面的效果完全相同:若一个祖先被找到 ( 如果存
在的话 ), 则把它写出来 。 接着调用 fail,程序失败 。 因此如果可能,
我们应追寻一个回溯点 。
?在追寻该回溯点时, 找到另一个祖先 ( 如果存在的话 ), 把它写出来,
然后再失败, 如此反复 。
?于是, 我们将找到并且输出所有的祖先, 直到最后不再有回溯点, 目
标执行失败 。
2004.11.3 AI程序设计 32
第 二 部分:第 5章 Prolog基础
5.5.5 副效应
? 这里有以下要点需要加以注意,
1) 目标本身不存在一个单一的解, 从而使我们所想要的全部解都作为副效应
形式给出;
2) 副效应在失败计算中也存在 。
? 这些情形是一个事物的两个方面, 但他们代表了不同阶层的观点 。 第一个
阶层乐观地表明可以利用的一些可能性, 而第二个阶层比较悲观, 提醒要
注意利用副效应, 因为即使当前目标不产生任何解, 它们照样也会完成 。
? 任何人学习 Prolog时, 迟早都会经历来自部分失败程序的意外输出的情况 。
也许下面这个建议可以对此有所帮助:将计算性代码与执行输入输出的代
码分开来 。
? 在我们上面的例子中, 所有的谓词都是计算谓词 。 它们会计算出一些家庭
关系 。 如果需要把解 ( 例如, 双亲, ) 写出来的话, 就构造另外一个谓词
来写出双亲, 而让这个谓词调用计算双亲的谓词 。
2004.11.3 AI程序设计 33
第 二 部分:第 5章 Prolog基础
5.5.6 小结
? 在这一节中, 我们学习了 Prolog的一些基本特性, 我们
学习了事实, 规则和目标, 学习了 Prolog的执行策略,
包括失败和回溯, 同样发现回溯可以给出多个结果, 最
后学习了副效应 。
2004.11.3 AI程序设计 34
第 二 部分:第 5章 Prolog基础
5.6 Prolog算符
? 本节继续介绍 Prolog编程的基本思想 。 在本节, 我们主要关心
Prolog中的数据在被操作之前, 是怎样被模型化的 。 因此, 并
没有太多关于代码执行的例子 。 我们假定读者已经熟悉了执行
策略 ( execution strategy) 对结果的影响和副效应 ( side
effects) 是怎样把一个 Prolog程序的逻辑转化为所需结果的 。
? 与上一节一样, 我们将继续使用 PIE环境来学习 Prolog,只有在
本书的后部分内容才有必要进入 Visual Prolog 6可视化开发环
境 VDE。
? 本节主要内容包括,
2004.11.3 AI程序设计 35
第 二 部分:第 5章 Prolog基础
5.6 Prolog算符
? 算符
? 深入理解算符
? 算符与谓词
? 算符作为参数
? 算符递归
? 算符使用策略
? 小结
2004.11.3 AI程序设计 36
第 二 部分:第 5章 Prolog基础
5.6.1 算符
? 在上一节, 所有的人都用名字 "Bill","John"和 "Pam"等表示 。 现在, "Bill"、
"John"和 "Pam"惟一代表各个人的名字 。 这些名字的值是简单数据类型或简
单论域 ( simple domains) 。 作为人名时, 这种简单论域的类型是字符串,
其它的简单论域可以是数 ( 如,123或 3.14), 符号 ( 如,xyz或 chi1_10)
和字符 ( 如,’ 5’或 ’ c’) 。
? 然而, 人是由包含名字在内的更多特性所表示的, 当我们需要表达所有特性而
不仅仅是名字时怎么办? 也就是说, 我们需要某些机制来表示复合论域
( compound domains), 它是更为简单论域的一个集合 。
? 在上一节, 我们曾试着通过向 PIE系统加入集中于实体 ( entities) 而不是关
系的事实把个人的多种特征放在一块儿, 比如名字 name和性别 gender,从
而给出了如下事实,
person("Bill","male"),
person("John","male"),
person("Pam","female"),
2004.11.3 AI程序设计 37
第 二 部分:第 5章 Prolog基础
5.6.1 算符
? 然而, 有一个更优雅的方法可以让我们把注意力集中在被表示的实
体上 。
? 我们可以将姓名和性别用被称为复合论域的形式封装在一个包中,
然后整个封装可以用一个 Prolog子句中的一个逻辑变量来表示, 像
其它任意变量一样 。 例如, 上述事实可以被一般地表示为,
person(Name,Gender)
? 注意,上面的句子既不是一个事实也不是一个谓词。逻辑上,它表
示程序中的名为 person的复合论域,每个 person有两个特征,由
逻辑变元 Name和 Gender表示。单词 person就是所谓的算符,变
元 Name和 Gender就是它的参数。以后应用这些算符来封装我们的
事实。
? 由于复合论域总有算符, 从此我们将用算符来代表各种复合论域 。
2004.11.3 AI程序设计 38
第 二 部分:第 5章 Prolog基础
5.6.1 算符
? 现在, 让我们用新定
义的算符 person改变
上节的第一个例子,
参见图 5.5。
? 注意, 在我们使用的
PIE( Prolog推理机 )
中, 我们可以直接使
用复合论域而不必事
先声明 ( 比如说, 我
们不必为了让代码运
行而定义一个名为
person 的谓词或事
实 ) 。
图 5.5 person算符
2004.11.3 AI程序设计 39
第 二 部分:第 5章 Prolog基础
5.6.1 算符
? 观察表示 father关系的两个事实, 可以发现人的定义比以前更丰富
了 ( 原来的代码已经被注释掉了 ——在 /*和 */之间 ) 。 这时, 每一
个人使用 person算符描述他们的名字和性别, 而在上一节, 我们只
使用了人的名字 。
? 更改代码后, 使用 Engine -> Reset,确保 PIE已经复位 。 然后, 用
Engine -> Reconsult重新求解 。
? 以前, 在对话窗口中的空白行上输入一个目标 ( 前面不带,?,
号 ) 。 如图 5.6所示的那样 。
2004.11.3 AI程序设计 40
第 二 部分:第 5章 Prolog基础
5.6.1 算符
图 5.6 目标代码
2004.11.3 AI程序设计 41
第 二 部分:第 5章 Prolog基础
5.6.1 算符
? 在行尾输入完句号后, 按键盘上的回车键, PIE将把行首到句号的文
本当作目标来执行, 然后就可以看到如图 5.7所示的结果 。
图 5.7 目标代码结果
2004.11.3 AI程序设计 42
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
? 看一下谓词 grandfather,就会发现一个微妙的问题, 一个人应该
有两个祖父:一个来自爸爸那边, 一个来自妈妈那边, 但按前面定
义的谓词 grandfather,只能得到从爸爸那边来的祖父 。
? 因此, 谓词 grandfather应该重写为,
grandFather(Person,TheGrandFather):-
parent(Person,ParentOfPerson),
father(ParentOfPerson,TheGrandFather),
? 这个谓词逻辑考虑到了任一双亲的父亲都是祖父的常识。
? 为了让它工作,需要再定义一个用到算符 person的谓词 father。这
个谓词将遍历系统中定义的 "parents"事实数据库。对于找到父亲们
来讲,这是一个比把他们作为事实列出来(如前所示)更为明智的
做法。因为通过后者可以用类似的形式来扩展概念,找出母亲。
2004.11.3 AI程序设计 43
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
? 可以写成下面两种形式中的任意一种,
/*1st version */
father(P,F),-
parent(P,F),
F = person(_,"male"),%Line 2
或者
/* 2nd version */
father(P,person(Name,"male")),-
parent(P,person(Name,"male")),
2004.11.3 AI程序设计 44
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
? 以上两个版本的逻辑是相同的, 但交给 Prolog推理机的方法不同 。
第一个版本中, Prolog依次检查代码中的 "parent"事实, 并看其是
否与从谓词头传递来的第一个逻辑变量 (p) 匹配 。 如果该变量确实
匹配, 则检查第二个参数是不是 person算符, 且 person算符的第
二个参数是不是字符串 "male"。
? 这个例子体现了算符的一个重要特性:一个算符的多个参数可以通
过常见的 Prolog变量和绑定值被分离和检查 (就像本例中的字符串精
确匹配 )。 在第二行 ( 第一个版本中用, %Line 2”注释的地方 ),
我们会发现仍用了一个匿名变量 ( 下划线 ) 作为 person算符的第一
个参数, 因为我们不关心 father具体叫什么名字 。
2004.11.3 AI程序设计 45
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
? 第二个版本与第一个版本功能相同 。 但在这里, 检查双亲事实集合
的同时, 找到正确的 P值, 就停下来并返回正确的算符给谓词头, 倘
若该谓词的第二个参数是字符串, male”,则该算符不再绑定到任
何 Prolog中间变量, 但在第一个版本不是这样 。
? 第二个版本比第一个要简洁, 但有时这种写法对初学者来说可能更
难读一些 。
? 现在, 让我们在一个具有 person事实的完整例子中使用这些代码,
如图 5.8所示 。
2004.11.3 AI程序设计 46
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
图 5.8 目标代码结果
2004.11.3 AI程序设计 47
第 二 部分:第 5章 Prolog基础
5.6.2 深入理解算符
? 把上面的代码输入 PIE中, 并给出如下目标,
grandFather(person("Pam","female"),W)
结果如图 5.9所示。
图 5.9 目标代码结果
2004.11.3 AI程序设计 48
第 二 部分:第 5章 Prolog基础
5.6.3 算符与谓词
? 从技术角度讲, 一个算符代表一个将多个论域绑定到一块儿的逻辑
功能 。 简言之, 算符是一种使 Prolog推理机把数据的各个部分放在
一起的一种机制, 它有效地把数据的各部分放在一个通用的盒子中 。
在需要时, 像前面的例子, 也可以获得其成分 。 它看起来也许像一
个 Prolog事实或一个谓词调用, 但它不是, 它只是一份数据, 一个
在很大程度上可以像一个字符串或数一样操作的数据 。
? 请注意, 算符与其它编程语言中的函数毫无关系 。 它不能够进行运
算 。 它只不过简单地代表复合论域并把自身的参数集成在一起 。
2004.11.3 AI程序设计 49
第 二 部分:第 5章 Prolog基础
5.6.3 算符与谓词
? 从通过研究上面的例子, 你会发现用逻辑变量代表由算符表示的数
据时无需做特别的操作 。 表示这些数据的变量可以像其它任何变量
一样书写:以大写字母开头 。 因此, 看一下本节例子中的
grandFather谓词, 你就会发现它与前面一节中定义的同一谓词完
全一样, 毕竟该谓词的逻辑没有改变 。 所以, 该谓词中所用的变量
也就没有任何变化 。
? 使用算符的最大好处在于, 修改代码时, 可以自由地改变算符的内
部参数而对使用该算符的谓词无需做大的改动 。
? 也就是说, 如果想让 person( 在后续版本中 ) 有更多的参数, 我们
仍然无需对谓词 grandFather做任何改动 。
2004.11.3 AI程序设计 50
第 二 部分:第 5章 Prolog基础
5.6.4 算符作为参数
? 上一节中, 算符 person有两个参数,Name和 Gender。 两者正好都是像常
量, Bill”和, Male”这样的简单论域 。 然而, 我们也可以把一个算符作为另
一个算符的参数 。
? 假定我们想对一对夫妇 ( 丈夫和妻子 ) 定义一个算符, 就可以使用这样一个
算符 。
myPredicate(ACouple):-
ACouple=couple(person(Husband,"male"),
person(Wife,"female")
),..,
? 本例中, 你会发现这个算符用另外两个算符来定义, 每一个算符都是变量和
常量的混合体 。 这正好反映了数据被描述的逻辑 。 这里所用的逻辑是丈夫总
是 "Male"而妻子总是 "Female",一对夫妇由一个丈夫和一个妻子组成 。 所
有这些都与通常的夫妇概念相一致 。
2004.11.3 AI程序设计 51
第 二 部分:第 5章 Prolog基础
5.6.4 算符作为参数
? 尽管在 PIE中, 我们无法预定义算符的实际语法类型, 但在 Visual
Prolog中可以这样定义 。 这样定义一个算符的好处在于, 当我们用
实际数据将算符实例化时, Prolog推理机将总是检查其类型的一致
性 。
? 这引出了算符的另一重要特性:算符 couple由两个参数定义且参数
的位置反映了他们的逻辑结合关系 。 第一部分中已经解释过了, 谓词
参数的位置由设计代码的程序员给出 。 一经给出, 就必须总是采用这
样的位置形式 。
? 该法则同样适用于算符的构造:例如算符 couple,我们恰好把
husband作为第一个参数, wife作为第二个参数 。 一旦这样做, 以
后无论何时用到此算符, 就必须保证 husband总在前面, 而 wife总
在后面 。
2004.11.3 AI程序设计 52
第 二 部分:第 5章 Prolog基础
5.6.4 算符作为参数
? 现在回头看一下算符 couple,有人会说, 如果不能保证第一个参数
总是一个丈夫 ( 总是男性 ), 且第二个总是一个妻子 ( 女性 ), 那应
该怎样定义它的参数? 这时, 可定义一个更简化的 couple算符如下,
myPredicate(ACouple):-
ACouple=couple(Husband,Wife),..,
? 因此,让我们把 husband和 wife颠倒过来用,但这一次我们是在不
知道哪个是 husband的位置,哪个是 wife的位置的情况下使用的。
2004.11.3 AI程序设计 53
第 二 部分:第 5章 Prolog基础
5.6.4 算符作为参数
myPredicate(Couple):-
Couple=couple(person(PersonsName,PersonsGender),
person(SpouseName,SpouseGender)
),..,
? 上面的算符中,下面的两个例子都具有逻辑意义,
myPredicate(C1):-
C1=couple(person("Bill","male"),person("Pam",
"female")),..,
/* or */
myPredicate(C2):-
C2=couple(person("Pam","female"),person("Bill",
"male")),..,
2004.11.3 AI程序设计 54
第 二 部分:第 5章 Prolog基础
5.6.4 算符作为参数
? 需要指出的是,在 PIE(和其它许多 Prolog推理机)中,通过算符的定义,
无法看出该算符是接受简单论域还是复合论域。这是由于在 PIE中,复合论
域可以不经声明而直接使用,这与程序员的想法不同。
? 例如,我们想用下面的复合论域,
person(Name,Gender)
? 那么 PIE将很容易接受像下面这样的逻辑变量,
regardingAPerson(Somebody):-
Somebody=person("Pam",
person("Pam",
person("Pam",
person("Pam","female")
)
)
),..,
? 实际上,这在当前上下文中没有任何逻辑意义。幸运的是,Visual Prolog
在区分简单论域和复合论域方面作了许多很好的工作,所以,看了后面的教
程,读者就不会有这些问题了。
2004.11.3 AI程序设计 55
第 二 部分:第 5章 Prolog基础
5.6.5 算符递归
? 使用算符描述数据时,可以像其它任何数据一样操作。例如,让我们
简短地回顾前面用到的谓词 ancestor。
? 为了确定某人是否是另一个人的先辈,我们使用递归定义了该谓词,
也就是说,引用了自身的定义。就像这样,
ancestor(Person,Ancestor),- parent(Person,Ancestor),
ancestor(Person,Ancestor),- parent(Person,P1),
ancestor(P1,Ancestor),
? 这个定义表示,父母是先辈,并且父母的先辈也是先辈。我们会发现,
上面的变量可以很好地代表简单论域或复合论域。
2004.11.3 AI程序设计 56
第 二 部分:第 5章 Prolog基础
5.6.5 算符递归
? 因而我们这样定义数据,
parent(person("Bill","male"),person("John","male")),
parent(person("pam","female"),person("Bill","male")),
? 如果现在让 PIE求解如下目标,
P=person("pam","female"),ancestor(P,Who)
将得到如图 5.10所示的结果。
2004.11.3 AI程序设计 57
第 二 部分:第 5章 Prolog基础
5.6.5 算符递归
? PIE推理机循环检查
parent事实来找到可
能的答案。顺便说一下,
在上面的求解中,你会
发现我们在给 PIE指定
目标时将复合论域
person绑定到变量 P。
其目的是用来让代码更
易读,但同时也演示了
我们可以将一份数据指
定为复合论域赋给任何
Prolog变量。
图 5.10 目标代码结果
2004.11.3 AI程序设计 58
第 二 部分:第 5章 Prolog基础
5.6.6 算符使用策略
? 有了模型化的数据,软件才会尽可能的好。建模的意思,就是建立外部
真实世界和软件内部数据结构的关系。如果数据模型很差,软件一般来
说也很差;或者最多也是没有效率。这对于任何的编程语言和任何软件
都是成立的。这也正是在前面的内容中,我们努力把注意力集中在实体
上并尽量不使用更多事实的原因。类似地,在本节,我们介绍了算符的
概念,以使被模型化的实体的数据更清晰。
? Prolog的好处在于,它可以用一种内部代码能够高效使用的形式来描述
客观数据。同时,它使代码对于同一项目组中的其他程序员更易读。
? 建模过程中,算符可以用来帮助建立任何类型的复合论域。在软件开发
的关键阶段,我们将必须仔细观察真实世界的多样性来设计步骤并用算
符转化它们,紧记他们的用法。
2004.11.3 AI程序设计 59
第 二 部分:第 5章 Prolog基础
5.6.6 算符使用策略
? 当我们把注意力集中到软件中要用到的算符(或其它数据结构)上时,
首先对它应该有一个全盘的认识。然后暂停下来审视软件中的每一个细
节。这样就能写出确实需要的算符。
? 仔细构造数据结构并不是惟一的要求。我们还需要(常常是同时的)写
或更改不同的目标及子目标(也就是谓词),从而充分应用那些谓词中
的数据。为了达到这些目标和子目标的副效应会让软件正确运行,我们
还可以从中得到有价值的反馈来改进自己的数据结构。
? 本节中,我们没有设计一个完整的软件。我们只是用一点点数据来建立
对真实世界中人与人(父母,祖父,家庭,先辈等等)的某些类型的数
据的感觉。紧接着,我们将用一条线把所学的东西串起来,直到最后以
一个用到这些数据结构的实用软件结束。
2004.11.3 AI程序设计 60
第 二 部分:第 5章 Prolog基础
5.6.7 小结
? 这一节,我们讲了数据可以由多个简单论域构成,也就是说数据可
以是用算符代表的复合论域。算符中参数的位置必须与他们所表示
的逻辑严格一致。而且算符不仅能以简单论域作为参数,也可以把
其它算符作为参数。算符所表示的数据可以像其它 Prolog变量一样
使用,甚至可以执行所有操作,包括递归。
? 我们还讲到了,必须花费时间为正在开发的软件,建立一些真实世
界的模型子集,获得对数据的感觉。同时我们应该用模型化的数据
对所要用的谓词进行测试,以期精益求精。 。
2004.11.3 AI程序设计 61
第 二 部分:第 5章 Prolog基础
本章小结
我们学习了关于 Prolog编程最基本的内容, 包括
Horn子句, Prolog推理机, 程序控制, Prolog算符等 。
这些内容也是 Visual Prolog的基础知识, 但在学习中应
注意两者之间的差异 。
2004.11.3 AI程序设计 62
第 二 部分:第 5章 Prolog基础
习 题
1,Horn子句指的是什么? 何谓 Horn子句逻辑?
2,Horn子句与 Prolog程序有什么关系?
3,首先装入 Visual Prolog 6随软件携带的例子, 然后用 Prolog推理机 ( PIE) 测试之 。
4,假设在 Prolog里已经包含了有关, 积木世界, 的下列事实与规则,
on(b,a),
on(c,b),
on(f,c),
on(c,d),
three_store(T,M,B):- on(T,M),on(M,B),
four_store(A,B,C,D):- three_store(A,B,C),three_store(B,C,D),
现在给出目标
- three_store(Fst,Snd,Trd),
试给出其求解过程 。 并通过其求解过程掌握 Prolog的执行策略, 包括, 回溯,,, 匹
配,,, 失败, 的含义 。
2004.11.3 AI程序设计 63
第 二 部分:第 5章 Prolog基础
习 题
5,某一人数众多的俱乐部的成员们被分成三组, 每组队员只能向同组或本组下
面的组中的成员挑战 ( 如果存在 ) 。
写出一个 Visual Prolog程序显示这种比赛方式下所有可能的比赛,
tom versus bill
marjory versus annette
例如, 使用截断确保
tom versus bill

bill versus tom
不能被同时显示 。
2004.11.3 AI程序设计 64
第 二 部分:第 5章 Prolog基础
习 题
6,编写一个尾部递归程序, 输出 2的幂值表, 如,
N 2^N
-- -----
1 2
2 4
3 8
4 16
,..,.,
10 1024
如这里显示这样, 使它在 10次幂结束 。
编写一个尾部递归程序, 使之可以接受一个数字作为输入, 并可以用两种方法之一结
束 。 它将通过自身数字反复相乘直到 81或大于 100的数为止 。 如果数值到达 81,它将
输出 yes。 如果数值超过 100,它将输出 no。