visual prolog 7练中学 - hnu.edu.cnkczx.hnu.cn/g2s/ewebeditor/uploadfile/20130513194758316.pdf...
TRANSCRIPT
-
Visual Prolog 7 边练边学
Contents
Visual Prolog 7 边练边学 ................................................................................................................. 1
第一章 简介 ................................................................................................................................. 5
第 1.1 节 创建一个新的 GUI 程序 ......................................................................................... 5
第 1.2 节 编译和运行你的程序 .............................................................................................. 6
第 1.3 节 玩一把:把程序的显示标题改成我的名字 .......................................................... 8
第 1.4 节 小结和说明 .............................................................................................................. 9
第 1.5 节 逻辑小知识:希腊人发明了逻辑 ........................................................................ 10
第二章 Forms ............................................................................................................................ 10
第 2.1 节 创建一个新的 Form .............................................................................................. 10
第 2.2 节 让任务菜单的项目工作起来 ................................................................................ 12
第 2.3 节 用 Code Expert 添加程序 .................................................................................... 13
第 2.4 节 小结和说明 .......................................................................................................... 15
第 2.5 节 逻辑小知识:亚里士多德的符号逻辑 ................................................................ 15
第三章 鼠标事件 ....................................................................................................................... 16
第 3.1 节 增加鼠标按下事件侦听器 .................................................................................... 16
第 3.2 节 onPaint 绘图 .......................................................................................................... 18
第 3.3 节 小结和说明 ............................................................................................................ 20
第 3.4 节 逻辑小知识:布尔代数 ........................................................................................ 21
第 3.5 节 论证的形式 ............................................................................................................ 21
第四章 基本图例 ....................................................................................................................... 22
第 4.1 节 任务菜单 ................................................................................................................ 22
第 4.2 节 目录树 .................................................................................................................... 23
第 4.3 节 创建一个工程项目 ................................................................................................ 25
第 4.4 节 创建一个新的 class ............................................................................................... 27
第 4.5 节 设置控件属性 ........................................................................................................ 28
第 4.6 节 小结和说明 .......................................................................................................... 29
第 4.7 节 逻辑小知识:谓词运算 ........................................................................................ 29
第五章 Horn 语句 ..................................................................................................................... 30
第 5.1 节 函数 ........................................................................................................................ 30
第 5.2 节 谓词 ........................................................................................................................ 30
第 5.3 节 求解 ........................................................................................................................ 30
第 5.4 节 答案不惟一 ............................................................................................................ 34
第 5.5 节 逻辑符号 ................................................................................................................ 36
第 5.6 节 Horn 句子 .............................................................................................................. 36
第 5.7 节 声明 ........................................................................................................................ 37
第 5.8 节 绘图谓词 ................................................................................................................ 38
第 5.8 节 GDI 实体 ................................................................................................................ 39
-
第 5.9 节 小结和说明 .......................................................................................................... 40
第 5.10 节 逻辑小知识:Horn 句子的意义 ......................................................................... 40
第六章 控制台应用 ................................................................................................................... 41
第 6.1 节 截断 ........................................................................................................................ 41
第 6.2 节 表 ............................................................................................................................ 43
第 6.3 节 表相关的几个话题 ................................................................................................ 48
第 6.4 节 字符串操作 ............................................................................................................ 52
第 6.5 节 逻辑小知识:谓词的语法 .................................................................................... 61
第七章 文本编辑器 ................................................................................................................... 61
第 7.1 节 建立文本编辑器程序 ............................................................................................ 61
第 7.2 节 文件保存 ................................................................................................................ 63
第 7.3 节 文件调入 ................................................................................................................ 64
第 7.4 节 编辑器的改进 ........................................................................................................ 66
第八章 绘图 ............................................................................................................................... 67
第 8.1 节 onPainting 相关的话题 ......................................................................................... 67
第 8.2 节 Custom Control 的应用 ......................................................................................... 72
第九章 数据类型 ....................................................................................................................... 75
第 9.1 节 原始数据类型 ........................................................................................................ 75
第 9.2 节 集合 ........................................................................................................................ 76
第 9.3 节 数字的集合 ............................................................................................................ 76
第 9.4 节 实数 ........................................................................................................................ 78
第 9.5 节 格式 ........................................................................................................................ 78
第 9.6 节 域 ............................................................................................................................ 79
第十章 如何用 Prolog 解决问题 .............................................................................................. 84
第 10.1 节 搜索一个表的所有元素 ...................................................................................... 84
第 10.2 节 寻求两个表的交集表 .......................................................................................... 86
第 10.3 节 两个表的合并 ...................................................................................................... 87
第 10.4 节 表的倒序排列 ..................................................................................................... 88
第 10.5 节 表的分割 ............................................................................................................. 90
第 10.6 节 表的快速排序 ..................................................................................................... 91
第 10.7 节 一年有多少天? .................................................................................................. 92
第 10.8 节 地图填色问题 ...................................................................................................... 93
第 10.9 节 翻子游戏 .............................................................................................................. 97
第 10.10 节 NIM 游戏 ........................................................................................................... 98
第十一章 事实 ......................................................................................................................... 106
第 11.1 节 数据库记录的生成和保存 ................................................................................ 106
第 11.2 节 数据库记录文件的读取 .................................................................................... 108
第 11.3 节 file 模块:读写字符串 readString/writeString .................................................... 109
第 11.4 节 常数 .................................................................................................................... 111
第十二章 class 和 object ......................................................................................................... 112
第 12.1 节 存取社会保障号 ................................................................................................ 112
第 12.2 节 Object 的事实 .................................................................................................... 114
第十三章 乌龟爬和语言处理 ................................................................................................. 115
第 13.1 节 乌龟爬行 ............................................................................................................ 115
-
第 13.2 节 乌龟的状态描述 ................................................................................................ 118
第 13.3 节 画出其他的规则图形 ........................................................................................ 119
第 13.4 节 保证每次画出的是一样的图形 ........................................................................ 121
第 13.5 节 递归 .................................................................................................................... 123
第 13.6 节 利用递归方法画复杂曲线 ................................................................................ 124
第 13.7 节 语言处理 ............................................................................................................ 127
第十四章 L-系统 ..................................................................................................................... 131
第 14.1 节 画一棵树 ............................................................................................................ 131
第 14.2 节 如何才能画一棵树 ............................................................................................ 133
第 14.3 节 画出不同的树 .................................................................................................... 136
第十五章 游戏 ......................................................................................................................... 137
第 15.1 节 游戏描述 ............................................................................................................ 137
第 15.2 节 游戏设计前的分析 ............................................................................................ 137
第 15.3 节 游戏编程 ............................................................................................................ 137
第 15.4 节 游戏的说明 ........................................................................................................ 144
第 15.5 节 游戏的改进 ........................................................................................................ 146
第 15.6 节 实体事实 Object Facts ....................................................................................... 149
第十六章 几个有趣的部件 ..................................................................................................... 150
第 16.1 节 TabControl 的应用 ............................................................................................. 150
第 16.2 节 ListControl 的应用 ............................................................................................ 155
第 16.3 节 treeView Control、listView Control .................................................................. 169
第十七章 动画 ......................................................................................................................... 177
第 17.1 节 绘图 .................................................................................................................. 178
第 17.2 节 定时器的使用 .................................................................................................... 179
第 17.3 节 动画是怎么实现的? ........................................................................................ 181
第 17.4 节 改进动画程序 .................................................................................................... 182
第十八章 相关资料介绍、索引 ............................................................................................. 183
第 18.1 节 Prolog 书籍 ........................................................................................................ 183
第 18.2 节 本书的例子程序的获得 .................................................................................... 185
第 18.3 节 中国的知识基系统研究 .................................................................................... 185
附录 - 要点索引 ..................................................................................................................... 185
自序....................................................................................................................................... 185
第一章 简介 ......................................................................................................................... 185
第二章 Forms ...................................................................................................................... 186
第三章 鼠标事件 ................................................................................................................. 186
第四章 基本图例 ................................................................................................................. 186
第五章 Horn 语句 ............................................................................................................... 187
第六章 控制台应用 ............................................................................................................. 188
第七章 文本编辑器 ............................................................................................................. 189
第八章 绘图 ......................................................................................................................... 189
第九章 数据类型 ................................................................................................................. 189
第十章 如何用 Prolog 解决问题 ........................................................................................ 190
第十一章 事实 ..................................................................................................................... 190
第十二章 class 和 object ..................................................................................................... 191
-
第十三章 乌龟爬和语言处理 ............................................................................................. 191
第十四章 L-系统 ................................................................................................................. 192
第十五章 游戏 ..................................................................................................................... 192
第十六章 几个有趣的部件 ................................................................................................. 192
第十七章 动画 ..................................................................................................................... 193
第十八章 相关资料介绍、索引 ......................................................................................... 193
-
正文 --------------------------------------
自序
本书是为 Visual Prolog 7 以上版本的编程爱好者而写。我们不谈大而空的理论,所以您不用
担心晦涩难懂;我们也不设立好高骛远的目标,所以您也不用担心看不懂。所以它完全是是
适用于自学的:一个人静静地摸索。让纷扰的世界去它的吧,把心沉浸在一个循序渐进的创
建、享受的过程中吧。
所以本书还适合那些不喜欢纷扰、喜欢独立思考的人们。
您可以在 www.visual-prolog.com 网站上下载一个免费 Visual Prolog 7 的版本,然后一切都
可以进行下去了。
我们拿一个个例子,来引导您走入 Visual Prolog 殿堂,来消除您对一个陌生语言 Visual Prolog
的隔膜和恐惧。
我坚信,逻辑语言适合中国人掌握,因为我们有庞大的人群,因为我们有五千年的文化,因
为我们有庞大的应用市场。未来是知识的时代,知识处理会是很重要的事,我们不能袖手旁
观!
我知道大学的计算机教育还把 Visual Prolog 5 作为实习的平台,然而它虽然好,但是已经不
符合现代化的编程理念了,但是 Visual Prolog 7 不同,它的理念是新的,是符合潮流的,class
和 objects,在这里一样通行;而开放的结构让它有很强的包容性。你不会浪费时间。
每个中国的青年和知识壮年,都该学习学习 Visual Prolog 7。需要更多信息,也可以访问
www.visual-prolog.cn ,我们会在这里不是更新信息,给 Visual Prolog 爱好者一个聚会的交
流的平台。
2009-1-4 于 北京
第一章 简介
首先我们要下载安装 Visual Prolog 7.1 免费个人版软件,可以正常启动,本书内容建立在这
个软件基础上。
第 1.1 节 创建一个新的 GUI 程序
我们开始创建一个新的程序,如图 1.1.1 所示,我们点击菜单项 Project|New,
http://www.visual-prolog.com/http://www.visual-prolog.cn/
-
图 1.1.1 这是起点
然后,根据提示,我们创建自己的 GUI 程序 myFirst:
图 1.1.2 简单填一个名字就可以开始写你自己的程序了
然后,IDE 编程环境进入到:
图 1.1.3 创建了第一个程序
这样就完成了“myFisrt”程序的创建,基本的框架已经有程序设计环境为你自动生成了。
第 1.2 节 编译和运行你的程序
在编程环境下,点击菜单项 Build||build,就可以编译你的程序,如果要执行程序,则点击
Build|Excute。编译时状态行显示编译进程信息:
-
图 1.2.1 编译时状态行显示编译进程,要耐心等一等
如果要执行的程序还没有编译过,则,点击 Excute 菜单项,如图 1.2.1 所示,则可以完成从
编译到执行的全部过程。
图 1.2.2 执行程序的菜单操作,也可以用工具栏的“E”来替代,经常用到
我们可以看到,我们刚刚建立的程序,运行了起来,如图 1.2.3 所示。
图 1.2.3 我的第一个 GUI 程序 myFirst, 也很不错吧?但是花不了 5 分钟啊!
可以看到,第一个程序虽然是空的,但是,却样样齐全:有主窗口、信息窗口、菜单系统、
工具栏和状态行等等基本的元素。
-
第 1.3 节 玩一把:把程序的显示标题改成我的名字
为了增加乐趣,我们可以考虑修改 myFirst 为我们自己的名字。比如如果我叫“汀郁 桂霞”,
我们让它显示这个,如何?首先我们关闭运行的程序,然后返回编程环境,看到项目文件树
如图 1.3.1 所示,发生了变化:
图 1.3.1 编译后的文件树
多出了一些文件。我们点击 TaskWindow 目录,找到 TaskWindow.win,并点击鼠标右键,看
到弹出菜单,如图 1.3.2 所示。
图 1.3.2 找到主窗口进行编辑
我们选择 Edit 项目,进行编辑,首先看到:
-
图 1.3.3 修改标题名字成为我的名字
我们把 Title 项目目前的 myFirst 修改为“汀郁 桂霞”,然后再执行一下程序,可以看到,程
序显示的标题已经发生了改变,如图 1.3.4 所示。你可以尝试改成你自己的名字,增加乐趣。
学习 Visual Prolog 是一个充满乐趣的事情,不要忘记了啊。
图 1.3.4 真的修改成我的名字了,很神奇吧?!
第 1.4 节 小结和说明
这里我们建立了第一个程序,在目录 myFirst 里。目前这里包含的文件和目录如图 1.4.1 所
示。
图 1.4.1 第一个程序的文件和目录列表
-
我们以后将在这个基础上,进行功能添加。这是创建一个程序的开端。
第 1.5 节 逻辑小知识:希腊人发明了逻辑
古希腊人相信这样的信条:
Our constitution does not copy the laws of neighboring states; we are rather a model to
others than imitators ourselves. Its administration favors the many instead of the few; this
is why it is called a democracy. If we look to the laws, they afford equal justice to all in their
private differences; if no social standing, advancement in public life falls to reputation for
capacity, class considerations not being allowed to interfere with merit; nor again does
poverty bar the way: If a man is able to serve the state, he is not hindered by the obscurity
of his condition from proposing laws, and political actions.
在这样一个社会里,一个人要想有所作为,想有所创新,必须首先说服大家。一个人的思想
一旦为大多数人接受,就可以成为法律或者大家普遍遵从的规则。大家行事只能按照惯例、
英雄们的做法来做。
聪明的古希腊哲人们为了说服别人,发明了逻辑方法、诡辩术、聚众心理学、说谎的艺术等
等技巧,来试图说服大众,证明自己的正确。
而 Visual Prolog 遵从的逻辑学方法,正式这样一个古老的方法的一种应用:用现存的正确命
题,经过不断的匹配和验证,证明一个新命题的正确。
第二章 Forms
上一章,我们学习了建立一个空的程序项目。这一章,我们学习怎么在这个基础上,为项目
增加一个 Form。Form 是窗口界面的基本元素,它也是一个窗口,相当于一个控制面板。在
这个面板上,我们可以增加一些控件:比如静态文本、文本输入框、编辑框、按钮、滑块、
列表框等等,从而实现对用户事件的反应。我们甚至可以在这个面板上绘图。
第 2.1 节 创建一个新的 Form
启动编程环境,打开已有的 Project 名字“myFirst”。在 myFirst 项目基础上,点击菜单项
File|New in new Package,如图 2.1.1 所示。
-
图 2.1.1 击菜单项 File|New in new Package,创建一个新的 Form
我们可以看到,出现“Create Project Item”对话框,我们在 Name 处填写“query”,在左边
侧面一列,选择 Form 项目,如图 2.1.2 所示。
图 2.1.2 Create Project Item 对话框 Name 处填写 query,选择 Form
然后点击下面的 Create 按钮,于是,一个新的 Form 就创建了,在文件树列表里,可以看到
多了一些东西,如图 2.1.3 所示。
图 2.1.3 文件树列表多了一些东西
并且自动进入 Form 编辑状态如图 2.1.4 所示。
图 2.1.4 自动进入 Form 编辑状态
图 2.1.5 所示是控件工具箱。这些控件都是可以放到 Form 这个控制面板上的零部件。我们
选择需要的,首先点击相应的图标,然后当鼠标移动到 Form 上方,一个阴影一样的图标就
在光标处,你可以用鼠标确定放在什么位置,放多大,这样就完成了一个控件的放置或者安
放。复杂的应用界面,就是这些一个一个标准的按钮组合起来完成的。
-
图 2.1.5 控件工具箱
图 2.1.6 所示是控件版面编辑安置工具箱。这个工具箱是帮助我们把控件在 Form 上安放、
编排地更合理,更有条不紊。它包含了左对齐、竖中心对齐、右对齐、显示网格线、上对齐、
下对齐、横中心对齐、等大小、等间距、等高、等宽等等工具。我们可以选中相应的控件,
然后使用这些功能,安排控件布局。
图 2.1.6 控件版面编辑安置工具箱。
需要说明的是,Form 的控件编辑是随时可以进行的。现在我们只要创建这个缺省的 query
Form 就行了,更多的工作留在以后进行。我们可以点击 query 的右上角关闭按钮,根据提
示保存 Form。
第 2.2 节 让任务菜单的项目工作起来
我们已经创建了一个 From,它的名字就是 query,如何在程序启动后,我们点击菜单 File|new,
就可以把这个界面调出来呢?
首先,如图 2.2.1 所示,我们在这里找到程序菜单项目所在文件。
-
图 2.2.1 程序菜单项目所在文件是 TaskWindow.mnu
然后我们按鼠标右键激活菜单,选择 Edit 来编辑菜单,如图 2.2.2 所示,我们选择 File|New
项目,把上部的 Disable 选择框的勾通过鼠标点击去掉,然后关闭这个对话框,根据提示保
存修改。
图 2.2.2 选择 File|New 项目,鼠标点击去掉 Disable 选择框的勾
第 2.3 节 用 Code Expert 添加程序
然后我们选择 TaskWindow.win 文件,点击鼠标右键,选择 Code Expert 菜单项,如图 2.3.1
所示。这样我们就可进入代码专家界面如图 2.3.2 所示。
-
图 2.3.1 TaskWindow.win 文件上鼠标右键选择 Code Expert 菜单项
我们在代码专家对话框里选择 File|New 菜单项目,可以采取鼠标双击或者选中项目后点击
右下角的 add 按钮,进入代码编写界面,程序自动生成的代码如图 2.3.3 所示。
图 2.3.2 双击或者点击 add 按钮
我们在自动生成的代码后写出相应的代码。
图 2.3.3 程序自动生成了一些代码
我们把自动生成的代码如下:
-
clauses
onFileNew(_Source, _MenuTag).
修改为代码如下所示:
clauses
onFileNew(W, _MenuTag) :- X= query::new(W), X:show().
然后,重新编译运行一下程序,注意,当程序提示是否加入 query 包,要选择 add all。最后
发现,当我们点击 File|New 菜单项后,程序显示了 query 对话框,如图 2.3.4 所示。
图 2.3.4 点击 File|New 菜单项程序显示 query 对话框
第 2.4 节 小结和说明
本章我们主要学习了怎么创建一个 Form,怎么把任务菜单项目变得可用,怎么利用代码专
家 Code Expert 功能来顺利的编写代码,最后实现了用菜单激活一个对话框的基本操作。这
是很重要的一步,也是编写程序的最基本操作。希望大家认真融会贯通。
第 2.5 节 逻辑小知识:亚里士多德的符号逻辑
亚里士多德认为,一个命题由主体和谓词两部分,外加修饰词构成。表示程度的修饰词有:
“每一个/都”(every)、“没有/绝不”(no)、“一些/某种程度上”(some)、“并非都”(not every),
几种。后来葡萄牙和西班牙的逻辑学家在这个基础上发明了符号逻辑表示方法,来简化表述
命题:
限定符号 含义 书写方法 举例
a Universal affirmative 完全肯定 PaQ Every P is Q
e Universal negative 完全否定 PeQ No P is Q
i Particular affirmative 部分肯定 PiQ Some P is Q
o Particular negative 部分否定 PoQ Some P is not Q
命题由此被分为不同真的、矛盾的、不同假的。下面是他们的解释:
a. 两个命题 P 和 Q 是矛盾的,当且仅当
i. P 和 Q 不能同时是正确的,并
-
ii P 或 Q 有一个是正确的。
类型 a 和类型 o 是矛盾的,同样类型 e 和类型 i 也是矛盾的。
b. 两个命题 P 和 Q 是相反的,当且仅当
i. P 和 Q 不能同时是真的,并
ii. P 和 Q 可以都是假的。
类型 a 和类型 e 是不同真的。
c. P 和 Q 是不完全相反的,当且仅当
i. P 和 Q 不能同时是假的,并
ii. P 和 Q 都可以是真的。
类型 i 和类型 o 是不同假的。
三段论是三个命题构成的证明过程:两个前提和一个结论,并且出现恰好三个论述,而且每
个论述被精确地运用两次。
设 P 代表结论的预言(主论述),S 是结论的主体(次论述),M 代表前提中的共同部分,但
M 并不出现在结论里,它是中间论述。包含 P 的前提称为大前提,包含 S 的前提称为小前
提。
第三章 鼠标事件
上一章,我们实现了点击 File|New 菜单项,弹出一个对话框 query,下面我们增加在这个对
话框内,对鼠标按下事件的反应。
第 3.1 节 增加鼠标按下事件侦听器
如图 3.1.1 所示,我们在目录树里找到 query.frm 文件,鼠标双击它,我们就可以进入这个对
话框 Form 的编辑状态。
-
图 3.1.1 找到 query.frm 文件,鼠标双击进入 Form 编辑状态
在 Form 编辑状态下,我们可以看到一个关于 query 的特性设置和事件设置对话框如图 3.1.2
所示,我们点击下部按钮 Events,进入下一页,并选择 MouseDownLisener 侦听器的反应谓
词为 onMouseDown。
图 3.1.2 设置鼠标按下事件侦听器
然后鼠标双击这个选项,则进入代码编辑界面。我们把原代码:
predicates
onMouseDown : drawWindow::mouseDownListener.
-
clauses
onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).
改写成为:
predicates
onMouseDown : drawWindow::mouseDownListener.
clauses
onMouseDown(S, Point, _ShiftControlAlt, _Button) :-
W= S:getVPIWindow(), Point= pnt(X, Y),
vpi::drawText(W, X, Y, "Hello, World!").
然后重新编译、执行一下程序,看到,每当我们在 query 对话框上点下鼠标,就会写出一段
文字“Hello, World!”, 如图 3.1.3 所示,这就表明我们实现了对鼠标事件的反应程序。
图 3.1.3 每当我们在框上点下鼠标,就会写出一段文字“Hello, World!”
第 3.2 节 onPaint 绘图
当然,像上节所述的鼠标点击绘图(文字)事件,也可以用另外一种方式来实现:即利用重
画功能 onPaint 来实现。具体操作步骤是,如图 3.2.1 所示,点击文件 query.pro,则右边框
内会出现该文件所包含的谓词名称列表,找到 onMouseDown/4 并双击,则转到代码的相应
处。
-
图 3.2.1 找到鼠标按下事件处理谓词的代码所在处
进入代码编辑器后,我们把上节编写的代码前后夹在“/*”、“*/”之间,使之变成说明文字,
然后添加我们编写的新的鼠标按下事件处理代码,最后代码部分变成如下所示:
/*
predicates
onMouseDown : drawWindow::mouseDownListener.
clauses
onMouseDown(S, Point, _ShiftControlAlt, _Button) :-
W= S:getVPIWindow(), Point= pnt(X, Y),
vpi::drawText(W, X, Y, "Hello, World!").
*/
class facts
mousePoint:pnt := pnt(-1, -1).
predicates
onMouseDown : drawWindow::mouseDownListener.
clauses
onMouseDown(_S, Point, _ShiftControlAlt, _Button) :-
mousePoint := Point,
Point= pnt(X, Y),
R= rct(X-8, Y-8, X+60, Y+8),
invalidate(R).
注意到这里我们用到数据库记录mousePoint来保存鼠标点击时的坐标数值,它的类型是 pnt,
即坐标结构;并把初始值设为 pnt(-1,-1)。这里还用到 invalidate(R),这是一个强迫启动重画
-
R 所指的矩形区域的谓词。R= rct(X-8, Y-8, X+60, Y+8)表示重画的矩形的左上角和右下角。
注意 X 代表水平方向,左到右,数值增加;Y 代表垂直方向,上到下,数值增加。它触发
重画事件,所以我们需要增加重画事件处理程序。如图 3.2.2 所示,增加 paintResponder 为
onPaint。
图 3.2.2 增加 paintResponder 为 onPaint
编写代码如下:
predicates
onPaint : drawWindow::paintResponder.
clauses
onPaint(_Source, _Rectangle, GDIObject) :-
if pnt(X, Y)= mousePoint, X>0
then
mousePoint := pnt(-1, -1),
GDIObject:drawText(pnt(X, Y), "Hello, World!", -1)
else succeed() end if.
同样实现了这样的功能。
第 3.3 节 小结和说明
这一章我们学习了如何处理一个事件,鼠标事件侦听器。实际上,鼠标事件可以区分出鼠标
是否在移动、按下左键还是右键、单击还是双击、按下还是弹起、是否按了中间键或者滑轮?
这样来做出不同的反应。在事件列表里,我们可以看到分成四类如图 3.3.1 所示。
图 3.3.1 鼠标事件分四类
就按下事件来讲,鼠标按下侦听器的参数定义如下:
drawWindow::mouseDownListener
-
mouseDownListener = (
drawWindow Source,
vpiDomains::pnt Point,
vpiDomains::keyModifier ShiftControlAlt,
integer Button) .
它可以返回所在绘图窗口、所在位置、是否按了 Shift、Control、Alt 等及其联合的修饰键、
以及是那个键发生的事件。这样便于控制。这些细节要查阅具体的定义。
第 3.4 节 逻辑小知识:布尔代数
1,符号“﹁”表示逻辑上“非”。﹁p 表示:当 p 为真,则﹁p 为假;当 p 为假,则﹁p 为
真。当为 p 公式,则﹁p 也是公式。
2,符号“∨”表示逻辑上“或”。p∨q 表示,p 或者 q。它的含义是:p 或者 q 任何一个为
真,p∨q 就为真;p、q 都为假,p∨q 才为假。
3,符号“∧”表示逻辑上“与”。p∧q 表示,p 与 q。它的含义是:p、q 任何一个为假,p
∧q 为假;p、q 都为真,p∧q 才为真。
4,p→q 表示逻辑上的“推定”。由 p 可以推定 q。它的含义是:p 为真,则 q 为真。
5,p≡q 表示逻辑上的“相当”。p 相当于 q。它的含义是:p 为真,则 q 为真;同样,q 为
真,则 p 为真。
通常我们用数字“1”表示“真”,而数字“0”表示“假”。这样我们可以用真值表来说明这
几个逻辑公式的含义如下:
p q ﹁p p∧q p∨q p→q ﹁p∨q
0 0 1 0 0 1 1
0 1 1 0 1 1 1
1 0 0 0 1 0 0
1 1 0 1 1 1 1
从这里我们可以看出,很容易证明,p→q 与﹁p∨q 是完全相当的,即:
(p→q)≡(﹁p∨q)
第 3.5 节 论证的形式
我们把一个论证写作:
p∨q
﹁p
├q
这一段论证说:因为 p∨q 是真,而且 p 为假,则可以插入事实 q 为真。符号“├”是插入
-
的意思。
在 Prolog 里,我们的证明方法是这样的:
q←p
p
├q
这就是说,p 推定 q,p 为真,则插入 q 为真。这就是 Prolog 的证明过程。
第四章 基本图例
这一章,我们通过建立一个工程 factorial 来解释说明一下,整个 VIP 编程环境 IDE 的关键
部件和基本图例,在以后的章节里,就不再重复了。
第 4.1 节 任务菜单
如图 1.1.1 所示,我们点击主菜单 Project|New 菜单项,就可以新建一个软件工程(Project)
了。主菜单,就是 IDE 的一个主要组成部分,如图 4.1.1 所示,有很多项目。
图 4.1.1 IDE 的主菜单有 14 个项目
“A|B”的描述,是我们对菜单项目点击顺序的一种描述,表示先点击菜单项 A 然后点击下
一层的 B。我们新建项目 factorial 要填写的项目如下:
General
Project Name: factorial
UI Strategy: Object-oriented GUI(pfc/gui)
Target Type: Exe
Base Directory: C:\vispro
注意,我们需要在 Prolog Setting 对话框下,对这几个项目做相应的填写或者修改,如图 4.1.2
所示。在这里,我们故意把存取我们这个程序文件的目录设置在 C 盘的 vispro 之下,其实
我们可以根据自己需要进行设置。然后点击 OK,就可以创建我们自己的工程 factorial 图形
界面程序了,如图 4.1.3 所示。
-
图 4.1.2 填写并更改所存目录
图 4.1.3 创建 factorial 工程
第 4.2 节 目录树
我们看到,新建的工程的目录树如图 4.2.1 所示。
-
图 4.2.1 文件目录树
这些目录双击可以打开,看到其中有些什么文件;而其中的文件,双击,就可以进入相应的
编辑状态。在文件项目上点击鼠标右键,则一般会弹出相应的弹出菜单。如果和上图一样的
操作,我们的描述就是:
用 Code Expert 为 TaskWindow/TaskWindow.win 添加代码
这个指令描述,包含以下步骤:
(1) 双击鼠标打开目录 TaskWindow
(2) 找到文件 TaskWindow.win
(3) 在这个文件上按鼠标右键,弹出菜单
(4) 在弹出菜单上,选择 Code Expert 菜单项
第 4.2.1 代码专家
代码专家也有一个树形的对话框,让你来选择,具体增加的编码内容。就之前对
TaskWindow.win 代码编写来讲,当我们启用了代码专家 Code Expert 项目后,进入一个对话
框,如图 4.2.2 所示,为了对菜单项目 File|New 进行编码,我们展开树形结构,找到 New 项
目,双击它就可以进行代码编写。
-
图 4.2.2 Code Expert 通过树形结构来选择具体编码的项目
在前面我们谈到的 Form 中,Code Expert 的实现只需要选择 Events 的项目,进行双击,就
可以实现其功能。所以 Code Expert 在不同的地方,有不同的启动方法,方便我们进行代码
设计。在 factorial 里,我们依然要设计一个 Form,名字也叫 query,这样,我们用 File|New
来启动这个对话框。这样增加代码如下:
predicates
onFileNew : window::menuItemListener.
clauses
onFileNew(W, _MenuTag) :- S= query::new(W), S:show().
而整个操作可以描述为一个简写的动作:
TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new,
而这代表着以下几个动作:
(1)树形目录操作:找到 TaskWindow.win 然后右键启动 Code Expert
(2)代码专家操作:进入树形结构点击 Menu,然后 TaskMenu,然后 id_file,然后 id_file_new
项目,双击进行代码编写。
第 4.3 节 创建一个工程项目
我们可以创建新的工程项目:比如新的代码包(Package)、新的模块(class)、新的对话框表
(Form)等等。我们这里要在新的包里创建一个名字叫 query 的 Form,首先进行如下在操
作:
File/New in New Package
我们创建一个程序包,显示的是一个独立的目录,名字叫做“FormContainer”,结果得到如
-
图 4.3.1 所示。
图 4.3.1 新的程序包 FormContainer
然后进行如下操作:
File/New in Existing Package
选择 FormContainer 程序包,创建 Form 名字为 query,如图 4.3.2 所示。
图 4.3.2 选择 Form 所在的程序包,可以看到可以用下拉列表选择
这样就进入 Form 的编辑过程。我们希望设计 query 如图 4.3.3 所示。
图 4.3.3 对话框设计
-
我们设计一个编辑控件,用来接收用户输入,而 factorial 按钮,用来发出计算输入数字的阶
乘的指令。
第 4.4 节 创建一个新的 class
我们要新增加一个模块(class),名字 fn,来实现按钮 factorial 按下的计算功能。如图:
图 4.4.1 在 formContainer 里增加 class fn
在文件 fn.cl 里编写如下代码实际上只有“calculate:(string) procedure.”是新增加的:
class fn : fn
open core
predicates
classInfo : core::classInfo.
calculate:(string) procedure.
end class fn
在 fn.pro 里编写如下代码:
implement fn
open core
clauses
classInfo("forms/fn", "1.0").
class predicates
fact:(integer, integer) procedure (i,o).
clauses
fact(0, 1) :- !.
-
fact(N, N*F) :-
fact(N-1, F).
calculate(X) :-
N= toterm(X),
fact(N, F),
stdio::write(F, "\n").
end implement fn
第 4.5 节 设置控件属性
我们设置控件属性,edit_ctrl 开始显示空的,factorial 按下事件反应调用 fn 的 calculate 来计
算结果。
图 4.5.1 编辑控件开始显示空的,等待输入
在编写 factorial 按钮事件代码之前必须 build 一下程序,让我们增加的东西进入程序的工程
管理库里。不然,我们在 factorial 按钮里看不到事件列表。我们如图 4.5.2 选择事件的 Code
Expert 功能,进入代码编写状态。
图 4.5.2 选择按钮事件的反应器
编写如下代码,就把按钮和模块 fn 的功能连接在一起。编写代码之前:
predicates
onFactorialClick : button::clickResponder.
clauses
-
onFactorialClick(_Source) = button::defaultAction.
编写代码之后:
predicates
onFactorialClick : button::clickResponder.
clauses
onFactorialClick(_S) = button::defaultAction() :-
fn::calculate(edit_ctl:getText()).
运行整个程序。看到,当我们输入 4,按 factorial 按钮,在 Message 里显示 24。这就是 4!
=4×3×2×1 的结果,如图 4.5.3 所示。
图 4.5.3 输入 4,输出结果 24,算出了阶乘得数
第 4.6 节 小结和说明
本章我们介绍了一些基本的编程、程序设计的叙述约定,及 IDE 的一些基本元素,这
样可以使我们以后的叙述更加简练,而不需要太多的图片来描述。我们介绍了程序包、模块
的增加方法,实现了一个完整的应用 factorial 的计算。介绍了 Code Expert 怎样帮助我们设
计代码。
第 4.7 节 逻辑小知识:谓词运算
为了逻辑运算,逐步发展了谓词运算,如下表示:
这样,逻辑叙述可以用谓词运算来描述了,这是 Prolog 的逻辑运算基础。
-
第五章 Horn 语句
第 5.1 节 函数
函数,是一个函数关系和一些参数构成的运算关系。比如 sin(X),函数关系是 sin,求正弦,
而 X 是参数。再如, mod(A,B),是描述 A 与 B 的模数关系。当我们描述函数时,总是可
以用常数值替代参数或变量,从而获得相应的函数计算结果。
函数的参数或变量的可能值定义范围,称为域(domain)。
有些函数的函子(或函数关系)写在函数参数之间。比如 5+3,和+(5,3)是一样的。
第 5.2 节 谓词
谓词是函数的一种特殊形式,它是特指那些值只可能为“真”或“假”的特殊函数。
X > Y,在 X 比 Y 大时为真,其他情况为假
X < Y,在 X 比 Y 小时为真,其他情况为假
X = Y,在 X 与 Y 相等时为真,其他情况为假
这里>、
-
city("Yellowstone", pnt(200, 100)).
那么这个谓词,就可以用来检验,给定的城市的坐标位置与已知的(数据库里预存的)信息
是否吻合,也可以用来找到指定城市的坐标,也可以用来根据某个坐标,寻找这个城市的名
称是什么。
city("Salt Lake", pnt(30, 40)) → true
city("Logan", pnt(100, 200)) → false
city("Provo", pnt(100, 200)) → true
city("Salt Lake", P) → P= pnt(30, 40)
我们通常约定大写字母开头的符号变量。比如:
X、Y、Wh、Who、B、A、Xs、Temperature、Humidity、Rate
Project Settings. 在VDE主菜单下点击Project/New 进入Project Settings 对话框
General
Project Name: mapDataBase
UI Strategy: Object-oriented (pfc/GUI)
Target Type: Exe
Base Directory: C:\vispro
Sub-Directory: mapDataBase\
创建Project Item:Form。我们在目录树的根目录下进行如下操作File/New in New Package,
选择Form,名字为“map”。在这个面板上,设置三个按钮Logan、Salt Lake、Provo,回头
用于显示这些城市。编辑窗口大小,留出画图的足够空间。
Build/Build 在主菜单下点击Build|Build菜单项,以便下一步操作,否则会出错。
Project Tree/TaskMenu.mnu使菜单项File/New可以操作。
Project Tree/TaskWindow.win/Code Expert 参照Menu/TaskMenu/id_file/id_file_new/
onFileNew 指令增加代码:
predicates
onFileNew : window::menuItemListener.
clauses
onFileNew(S, _MenuTag) :-
X= map::new(S), X:show().
这是为 File|New 菜单项编写代码的。然后在进行 Buile|Build 操作。
增加 draw 模块 class File/New in New Package 指令,选择 class 增加 draw。在 draw.cl 文件
-
里增加代码后如下:
class draw : draw
open core, vpiDomains
predicates
classInfo : core::classInfo.
drawThem:(windowHandle, string) procedure.
end class draw
在文件 draw.pro 里,增加代码后如下所示:
implement draw
open core, vpiDomains, vpi
constants
className = "draw".
classVersion = "".
class facts
city:(string, pnt).
clauses
classInfo(className, classVersion).
clauses
city("Salt Lake", pnt(30, 40)).
city("Logan", pnt(100, 120)).
city("Provo", pnt(100, 200)).
city("Yellowstone", pnt(200, 100)).
drawThem(Win, Name) :-
B= brush(pat_solid, color_red),
winSetBrush(Win, B),
city(Name, P),
!,
P= pnt(X1, Y1),
X2= X1+20, Y2= Y1+20,
drawEllipse(Win, rct(X1, Y1, X2, Y2)).
drawThem(_Win, _Name).
end implement draw
-
然后,编辑map对话框,map.frm/ Edit /Button Logan/Click Responder : onLoganClick 增加按钮
Logan的按下事件,增加如下代码:
predicates
onLoganClick : button::clickResponder.
clauses
onLoganClick(S) = button::defaultAction() :-
Parent= S:getParent(),
P= Parent:getVPIWindow(),
draw::drawThem(P, "Logan").
运行一下,点击 Logan 按钮,如图 5.3.1 所示,得到:
图 5.3.1 按钮 Logan 按下,画出城市坐标位置示意图
其他几个城市的按钮以可以如法炮制。如图 5.3.2 所示,我们画出了三个城市的坐标位置。
图 5.3.2 按下三个摁钮,画出三个城市的坐标位置示意图
他们的代码是:
predicates
onSaltLakeClick : button::clickResponder.
-
clauses
onSaltLakeClick(S) = button::defaultAction() :-
Parent= S:getParent(),
P= Parent:getVPIWindow(),
draw::drawThem(P, "Salt Lake").
predicates
onProvoClick : button::clickResponder.
clauses
onProvoClick(S) = button::defaultAction() :-
Parent= S:getParent(),
P= Parent:getVPIWindow(),
draw::drawThem(P, "Provo").
第 5.4 节 答案不惟一
上面我们遇到的谓词 city/2 是只有一个解的情况,实际上,我们要设计一个 conn/2,来表示
两个城市连接的描述:
conn(pnt(30, 40), pnt(100, 120)).
conn(pnt(100, 120), pnt(100, 200)).
conn(pnt(30, 40), pnt(200, 100)).
这样,我们往往遇到 2 个以上的答案:
conn(pnt(30, 40), W). → W= pnt(100, 120)
→W=pnt(200, 100)
conn(X, Y). →X=pnt(30, 40)/Y=pnt(100, 120)
→ X= pnt(100, 120)/Y=pnt(100, 200)
→X=pnt(30, 40)/Y=pnt(200, 100))
当我们提出问题:
conn(pnt(30, 40), W)?
那么显然我们可以有两个答案:W= pnt(100, 120) 或者 W=pnt(200, 100)。
第 5.4.1 节 一个采用多解谓词的程序
我们利用多解特性,利用 conn/2 来画出城市连接线,结果如图 5.4.1 所示。
-
图 5.4.1 画出连接线,利用 conn/2 的多解特性
我们增加按钮 drawConnections,并对他进行编码:
predicates
onDrawConnections : button::clickResponder.
clauses
onDrawConnections(S) = button::defaultAction :-
Parent= S:getParent(),
P= Parent:getVPIWindow(),
draw::drawConnections(P).
在模块 draw 的 draw.pro 文件里增加:
class facts
conn : (pnt, pnt).
clauses
conn(pnt(30, 40) , pnt(100, 120)).
conn(pnt(100, 120), pnt(100, 200)).
conn(pnt(30, 40), pnt(200, 100)).
drawConnections(Win) :- conn(P1, P2), drawLine(Win, P1, P2), fail.
drawConnections(_Win).
并修改原来画城市的语句为:
drawThem(Win, Name) :-
B= brush(pat_solid, color_red),
winSetBrush(Win, B),
city(Name, P),
!,
P= pnt(X1, Y1),
X2= X1+10, Y2= Y1+10,
drawEllipse(Win, rct(X1-10, Y1-10, X2, Y2)).
-
在 draw.cl 里增加:
drawConnections:(windowHandle) procedure.
这样,我们利用 drawConnections(Win) :- conn(P1, P2), drawLine(Win, P1, P2), fail.的强制回溯
功能,把所存的连接数据全部画出了。注意,有些城市因为没有设置按钮,并没有画出来。
如图 5.4.1 所示。
第 5.5 节 逻辑符号
“P1 与 P2”,这里“与”就是逻辑符号。这表明,只有 P1 和 P1 同时为真, “P1 与 P2”
才为真。在 Prolog 语言里,用逗号代表“与”(and);用分号代表“或”(or)。这里“与”、
“或”,就是逻辑符号。
这样如果我们描述(X>4)与(Y4),(Y
-
drawThem(Win) :- connections(Win), drawCities(Win).
“:-”符号之前的部分,我们称其为规则的“头”,之后的部分,称其为规则的“尾”,或者
规则的“体”。在这里是 drawThem(Win)头,而是“connections(Win), drawCities(Win)”
尾。头和尾之间用“:-”符号连接,末尾,用“.”表示结束。
第 5.7 节 声明
谓词定义如果没有输入输出流向说明,则其声明是不完整的。我们来看一个完整的谓词声明:
predicates
drawThem:(windowHandle) procedure (i).
这个声明说明:drawThem 的参数,是输入的,是属于 windowHandle 类型的。通常情
况下,你也可以不声明输入输出模式,而编译器会自动推理出合适的流模式。比
如我们也可以定义成这样:
class predicates
connections:( windowHandle).
drawCities:(windowHandle).
当然,Horn 句子也可以定义成事实的格式:
class facts
city:(string Name, pnt Position).
conn:(pnt, pnt).
稍后我们可以会看到,事实(facts)是可以插入、删除的,它的参数类型说明和谓词的类型
说明相同。关于类型说明,我可以用直接指明类型声明所在的模块的形式,如下:
class facts
conn:(vpiDomains::pnt, vpi::Domains::pnt).
也可以在 open 语句里,把相应的模块打开,这样,类型就不需要前缀来指明位置了。注意
这里 pnt 用到两个不同的位置的说明,但从形式上,是一样的。
open core, vpiDomains, vpi
确定式声明 你可以用以下几种声明,来明确指出谓词是有一种解、多种解:
determ —— 指明这个谓词或真、或假,而且只有一种可能。
procedure —— 说明这个谓词总是成功的。通常我们希望一些比较确定的过程,用到这样
-
的说明,比如前面我们定义的谓词:
class predicates
connections:( windowHandle) procedure (i).
drawCities:(windowHandle) procedure (i).
multi —— 用到这样说明谓词,从事成功的,并且有多种解答。
nondeterm —— 这样说明的谓词或事实,可能成功,也可能失败,有多种可能性。比如:
class facts
city:(string Name, pnt Position) nondeterm.
conn:(pnt, pnt) nondeterm.
我们可以利用这类谓词的特性,设计回溯过程比如:
connections(Win) :-
conn(P1, P2),
drawLine(Win, P1, P2),
fail.
connections(_Win).
这里,conn(P1, P2)提供两个点 P1、P2,而 drawLine(Win, P1, P2)就利用这两个点,画线。
遇到 fail 谓词,Prolog 就强制回溯,寻找下一个解,直到 conn(P1, P2)也失败,那时,
connections(_Win)是总要成功的,保证了 connections/1 总能成功。
第 5.8 节 绘图谓词
计算机界面很重要部分就是绘图输出。前面我们遇到几个绘图谓词,比如 drawLine/3,它的
用法是:
drawLine(Win, P1, P2)
它用来在点 P1 和 P2 之间划一条直线。而 P1、P2 用 pnt(X, Y)来表示。而画椭圆就是用到这
样的谓词:
drawEllipse(W, rct(X1, Y1, X2, Y2))
这里 rct(X1, Y1, X2, Y2)表示一个矩形,用左上角坐标 X1, Y1 与右下角坐标 X2, Y2 来表示。
而 W、Win 在这里代表要绘画窗口的句柄(WindowHandle),这不过是告诉程序,在哪里绘
图,在窗口系统,用句柄来代表窗口。
-
第 5.8 节 GDI 实体
其实,绘图除了用句柄之外,还可用到 GDI objects,也就是 GDI 实体,来绘图。比如,前
面的例子里,如果我们用 onPaint 谓词来处理事件 paintResponder,如图 5.8.1 所示,它的编
码如下所示:
clauses
onPaint(_S, _Rectangle, GDIObject) :-
draw::drawThem(GDIObject).
图 5.8.1 增加 onPaint 谓词作为 PaintResponder
那么我们可以重新改写 drawThem 谓词的声明,在 draw.cl 里这样声明:
class draw
open core
predicates
drawThem:(windowGDI).
end class draw
在 draw.pro 里,谓词重新改写后如下:
implement draw
open core, vpiDomains, vpi
class facts
city:(string Name, pnt Position).
conn:(pnt, pnt).
class predicates
connections:( windowGDI).
drawCities:(windowGDI).
clauses
city("Salt Lake", pnt(30, 40)).
city("Logan", pnt(100, 120)).
city("Provo", pnt(100, 160)).
conn(pnt(30, 40) , pnt(100, 120)).
-
conn(pnt(100, 120), pnt(100, 160)).
drawCities(W) :-
city(N, P),
P= pnt(X1, Y1),
X2= X1+10, Y2= Y1+10,
W:drawEllipse(rct(X1, Y1, X2, Y2)),
W:drawText(pnt(X1, Y1), N), fail.
drawCities(_Win).
connections(W) :-
conn(P1, P2), W:drawLine(P1, P2), fail.
connections(_W).
drawThem(Win) :-
connections(Win), drawCities(Win).
end implement draw
这里最大的变化是绘图语句 W:drawEllipse(rct(X1, Y1, X2, Y2))这样的引用与前面的引用有
很大不同 drawEllipse(W, rct(X1, Y1, X2, Y2))。
drawCities(W) :-
city(N, P),
P= pnt(X1, Y1),
X2= X1+10, Y2= Y1+10,
W:drawEllipse(rct(X1, Y1, X2, Y2)),
W:drawText(pnt(X1, Y1), N), fail.
drawCities(_Win).
第 5.9 节 小结和说明
我们这里介绍了谓词的声明、绘图语句及两种不同的方法,我们还介绍了 Horn 句子,
介绍了谓词的多解性,这里就慢慢接近真实的编程了。
第 5.10 节 逻辑小知识:Horn 句子的意义
Horn 句子:
H :- T1, T2, T3…
它的意义是:
-
如果 T1、T2、T3……分别都为真,则 H 为真。
我们需要记住“:-”代表“如果”;而逗号“,”代表“与”,分号“;”代表“或”。它们和谓
词的混合语句,构成了 Horn 句子,表达一定的逻辑描述,也称为“规则”。
第六章 控制台应用
许多人喜欢图形界面,但是控制台界面虽然只有文字描述,但是更能说明程序的核心逻辑和
计算方法,这一章,我们就用控制台应用(Console Application)来说明几个关键的 Prolog
元素。
第 6.1 节 截断
我们常常需要强制程序回溯,在找到一个答案之后,再去寻找另外一个,在前面我们介绍过
的 fail 就是起这样功能。但是相反,有时候,我们却不需要它回溯,不需要再去寻找下一条
规则、寻求另外的答案,这是我们就要用到“截断”(cut),我们在 Prolog 里,用叹号“!”
来表示。比如以下的阶乘递归算法描述:
factorial(0)→1
factorial(n) → n ×factorial(n -1)
用 Horn 子句来描述,可以表达为:
fact(N, 1) :- N
-
图 6.1.1 创建控制台应用
然后 Build/Build,编辑 main.pro 文件,这个文件开始的内容如下:
implement main
open core
constants
className = "main".
classVersion = "".
clauses
classInfo(className, classVersion).
clauses
run():-
console::init(),
succeed(). % place your own code here
end implement main
经过编辑后 main.pro 如下所示:
implement main
open core
class predicates
fact:(integer N, integer Res) procedure (i,o).
clauses
classinfo("facfun", "1.0").
fact(N, 1) :- N
-
fact(X, F),
stdio::write("控制台应用输出结果 ", " 输入数字= ",X, " 其阶乘= ", F),
stdio::nl,
!,
run().
run(). %按 Ctrl+Break 结束
end implement main
goal
mainExe::run(main::run ).
运行后,我们输入一个数字,就得到一个阶乘结果如图 6.1.2 所示,直至我们同时按下 Ctrl
键和右上角的 Break 键结束。
图 6.1.2 阶乘计算结果
第 6.2 节 表
表是一群具有相似特征的元素的集合体,我们把其相似的特征成为元素的类型,而整个表用
方括号括起来、用逗号相间隔的形式来表示,其类型用元素类型后加星号“*”来表示。每
个不是空表的表,都会有表头和表尾;空表是没有一个元素的表。例如:
List Type Head Tail
[3, 4, 5, 6, 7] integer* 3 [4, 5, 6, 7]
["wo3", "ni3", "ta1"] string* "wo3" ["ni3", "ta1"]
[4] integer* 4 []
[3.4, 5.6, 2.3] real* 3.4 [5.6, 2.3]
我们可以把任何不是空表的表写作:
[Head | Tail ]
其中,head 是一个元素,Tail 是一个表。所以,你可以看到下面的匹配过程:
-
Pattern List X Xs
[X|Xs] [3.4,5.6,2.3] X = 3.4 Xs = [5.6,2.3]
[X|Xs] [5.6,2.3] X = 5.6 Xs=[2.3]
[X|Xs] [2.3] X = 2.3 Xs = []
[X|Xs] [] Does not match.
同样,我们可以写出更多的元素,要求表匹配,从而进行删选,比如我们假定表形式为[X1,X2
| Xs],这就是说,至少有两个元素的表,那么,2 个元素以下的表,就不匹配:
Pattern List X1,X2 Xs
[X1,X2|Xs] [3,5,2,7] X1 = 3,X2 = 5 Xs = [2,7]
[X1,X2|Xs] [2,7] X1 = 2,X2 = 7 Xs=[]
[X1,X2|Xs] [7] Does not match.
[X1,X2|Xs] [] Does not match.
我们通过实例来体会表的处理,如下建立一个应用“avg”:
General
Project Name: avg
UI Strategy: console
先 Build 一下,然后编辑 main.pro 最后如下所示:
implement main
open core, console
domains
rList= real*.
class predicates
len:(rList, real) procedure (i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
run():-
console::init(),
List= read(),
len(List, A),
write(A), nl,
run().
-
end implement main
goal
mainExe::run(main::run).
运行后如图 6.2.1 所示:
图 6.2.1 正确输入一个实数表,则返回长度数字
但是当不是表,或者不是实数表,程序会出错返回。下面我们还可以加入 sum/2 来计算所有
元素的和:
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
我们分析一下 sum([3.4, 5.6, 2.3], S)的计算经过:
1. sum([3.4, 5.6, 2.3], S)匹配
sum([X|Xs], S+X) .- sum(Xs, S), with X=3.4, Xs=[5.6, 2.3],
得到 sum([3.4, 5.6, 2.3], S[5.6,2.3] + 3.4) .- sum([5.6, 2.3], S[5.6,2.3])
2. sum([5.6, 2.3], S[5.6,2.3])匹配
sum([X|Xs], S+X) .- sum(Xs, S), with X=5.6, Xs=[2.3],
得到 sum([5.6, 2.3], S[2.3] + 5.6) .- sum([2.3], S[2.3])
3. sum([2.3], S[2.3]) 匹配
sum([X|Xs], S+X) .- sum(Xs, S), with X=2.3, Xs=[],
得到 sum([2.3], S[] + 2.3) .- sum([], S[])
4. sum([], S[]) 匹配 sum([], 0.0) 得到 S[] = 0.
我们可以让程序写出过程如下图 6.2.2 所示。
-
图 6.2.2 计算一个表的元素之和的过程
相应的程序如下所示:
implement main
open core, console
domains
rList= real*.
class predicates
len:(rList, real) procedure (i, o).
sum:(rList, real) procedure (i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
sum([], 0) :- !.
sum([X|Xs], S+X) :-
write("now list = ", [X|Xs],"\n"),
sum(Xs, S),
write("now result =", S, "\n").
-
run():-
console::init(),
List= read(),
len(List, A),
write(A), nl,
sum(List,X),
write("Final result = ", X),nl,
run().
end implement main
goal
mainExe::run(main::run).
当然,我们还可以用另外一种办法来计算元素之和:
add([], A, A).
add([X|Xs], A, S) :- add(Xs, X+A, S).
但是我们调用的时候需要调用 add(List,0,Sum)。而说明此谓词
add:(rList, real, real) procedure (i, i, o).
程序如下:
implement main
open core, console
domains
rList= real*.
class predicates
len:(rList, real) procedure (i, o).
add:(rList, real, real) procedure (i, i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
add([],A,A ) :- !.
add([X|Xs],A , Sum) :-
-
write("original list=", [X|Xs],"result =", A,"\t\t now result=", A+X, "\n"),
add(Xs, A+X, Sum).
run():-
console::init(),
List= read(),
len(List, A),
write(A), nl,
add(List,0,X),
write("Final result = ", X),nl,
run().
end implement main
goal
mainExe::run(main::run).
运行结果如图 6.2.3 所示。
图 6.2.3 另外一种计算方法
第 6.3 节 表相关的几个话题
表示一个很重要的计算工具,我们这里要介绍几个比较重要的表相关的计算话题。
简化 Reduction
一个表,可以用于计算每个元素的和,这个过程,被称为简化过程(reduction),因为这个过
程是把输入的维数减少了。我们可以把表看成是一维数据,而和结果是零维数据,求和过程
是 1 维变 0 维的过程。有两种方法可以达成目标:递归式简化(recursive)和重复式简化
(iterative)。我们展示这两种方法如下:
%Recursive reduction
class predicates
sum:(real*, real) procedure (i, o).
-
clauses
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
递归式是假定知道表尾的结果,推理获得当前表的结果,以此类推,而总是有一个表的结果
是可知的,然后回退计算出最终结果;重复式就是不断重复累加的过程,更接近人们计算的
逻辑。
%Iterative reduction
red([], A, A) :- !.
red([X|Xs], A, S) :- red(Xs, X+A, S).
sumation(Xs, S) :- red(Xs, 0.0, S).
当我们希望计算元素的乘积时,可以替换得到如下程序:
%Iterative reduction
red([], A, A) :- !.
red([X|Xs], A, S) :- red(Xs, X*A, S).
product(Xs, S) :- red(Xs, 0.0, S).
我们看到,比较两个代码,它们有很大的相似性:除了“X+A”和“X*A”的差别之外,两
个计算过程几乎相似。在 Visual Prolog 里,我们就可以采用更高一级的算法描述,来实现两
种不同算法的统一。我们定义 red/4,代码如下:
implement main
open core, console
domains
pp2 = (real Argument1, real Argument2) -> real ReturnType.
clauses
classInfo("main", "hi_order").
class predicates
sum : pp2.
prod : pp2.
red : (pp2, real*, real, real) procedure (i, i, i, o).
clauses
sum(X, Y)= Z :- Z=X+Y.
prod(X, Y)= Z :- Z=X*Y.
red(_P, [], A, A) :- !.
red(P, [X|Xs], A, Ans) :-
red(P, Xs, P(X, A), Ans).
run():- console::init(),
List =read(),
red(prod, List, 1, Product),
red(sum, List, 0, Sum),
write("\n 您输入的实数表是", List,
-
"\n 其各个元素的乘积结果=", Product,
"\n 其各个元素的累加结果=", Sum), nl,
run().
end implement main
goal
mainExe::run(main::run
我们运行一下,可以看到运行结果如下,当你输入一个实数表,得到不同的输出结果,如图
6.3.1 所示。
图 6.3.1 更高形式的统一算法
ZIP
一个很有名的算法是,求两个表的点积(dotproduct),算法描述如下:
class predicates
dotproduct:(real*, real*, real) procedure (i, i, o).
clauses
dotproduct([], _, 0) :- !.
dotproduct(_, [], 0) :- !.
dotproduct([X|Xs], [Y|Ys], X*Y+R) :- dotproduct(Xs, Ys, R).
这里,我们分析 X*Y+R,实际上,可以看作两个数相乘、两个数相加的组合。这样,我们
可以利用前面的成果,写出另外一种算法,设计一个 zip/4:
zip(_P, [], _, []) :- !.
zip(_P, _, [], []) :- !.
zip(P, [X|Xs], [Y|Ys], [Z|As]) :-
Z= P(X, Y),
zip(P, Xs, Ys, As).
-
得到乘积表后,在计算这个表的元素相加的结果,利用前面的代码:
dotproduct(Xs, Ys, D) :-
zip(prod, Xs, Ys, Z),
red(sum, Z, 0, D).
我们最后设计代码如下:
implement main
open core, console
domains
pp2 = (real Argument1, real Argument2) -> real ReturnType.
clauses
classInfo("main", "hi_order").
class predicates
sum : pp2.
prod : pp2.
red : (pp2, real*, real, real) procedure (i, i, i, o).
zip : (pp2, real*, real*, real*) procedure (i, i, i, o).
dotproduct : ( real*, real*, real) procedure ( i, i, o).
clauses
sum(X, Y)= Z :- Z=X+Y.
prod(X, Y)= Z :- Z=X*Y.
red(_P, [], A, A) :- !.
red(P, [X|Xs], A, Ans) :-
red(P, Xs, P(X, A), Ans).
zip(_P, [], _, []) :- !.
zip(_P, _, [], []) :- !.
zip(P, [X|Xs], [Y|Ys], [Z|As]) :-
Z= P(X, Y),
zip(P, Xs, Ys, As).
dotproduct(Xs, Ys, D) :-
zip(prod, Xs, Ys, Z),
red(sum, Z, 0, D).
run():- console::init(),
write("\n 请输入第一个实数表:"), List1 =read(),
write("请输入第二个实数表:"), List2 =read(),
dotproduct(List1, List2, Product),
write("\n 您输入的实数表是", List1, "和", List2,
"\n 其点积结果=", Product), nl,
run().
end implement main
goal
mainExe::run(main::run).
-
运行后如图 6.3.2 所示。
图 6.3.2 计算两个表的点积
第 6.4 节 字符串操作
这里我们将通过几个字符串操作的例子,展示如何在 Visual Prolog 里处理数据结构问题。
Fronttoken concatlist concat/2/3
Fronttoken 是一个用途广泛的字符串操作谓词。看看下面的描述的结果会是怎样?
frontToken("break December", T, R)
实际上我们得到 T="break"、R=" December"。这里我们设计一个小程序,来展示 fronttoken
的效果,运行结果如图 6.4.1 所示。
implement main
open core, string, console
class predicates
tokenize:(string, string_list) procedure (i, o).
clauses
classInfo("main", "string_example").
tokenize(S, [T|Ts]) :-
frontToken(S, T, R),
!,
tokenize(R, Ts).
tokenize(_, []).
run():-
console::init(),
-
L= ["it ", "was ", "in ", "the ",
"bleak ", "December!"],
S=concatList(L),
UCase= toUpperCase(S),
RR= concat("case: ", Ucase),
R1= concat("It is ", "upper ", RR),
write(R1), nl,
tokenize(R1, Us),
write(Us),
_X = readline(), nl.
end implement main
goal
mainExe::run(main::run).
这里用到 concat/2、concat/3,它们是把 2 个或三个字符串按顺序连接在一起,中间不会有任
何增添。而 concatlist 则是把字符串表的每个元素按,按照顺序连接在一起,而且字符串和
字符串之间,增加一个空格作为间隔,形成一个字符串。toUpperCase 是把字符串的所有字
符转换成为大写字母。
图 6.4.1 fronttoken 把字符串分解成一个一个所谓的 token
toterm
我们还常常希望把输入的代表某个命令或功能的字符串,转换成一个可执行的术语(term),
这时,我们常常用到 toTerm。下面的例子里,Sx= stdio::readLine()从命令行读取一个字符串
到 Sx 里然后 toTerm 把这个字符串转换成实数,这里 hasdomain(real, IX) 是为了确保 toTerm
转换的结果是我们要的实数。运行结果如图 6.4.2 所示。
implement main
open core
clauses
classinfo("main", "toTerm_example").
run():-
console::init(),
Sx= stdio::readLine(),
hasdomain(real, IX),
IX= toTerm(Sx),
stdio::write("\n 你输入的数字 ", IX," 的平方值是:", IX^2,"\n"), run().
-
end implement main
goal
mainExe::run(main::run)
图 6.4.2 输入的字符串转换为实数,计算平方值
hasdomain
hasdomain 甚至于可以接受类似于表等更复杂的数据类型,下面的例子,我们制定一个整数
表,注意的是 hasdomain 不接受“*”的表表示方法,这里我们不能够用“integer*”,而要
用 core::integer_list,这是预先定义的整数表数据类型。
implement main
open core
clauses
classInfo("main", "hasdomain_example").
class predicates
sum:(integer*, integer) procedure (i, o).
clauses
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
run():- console::init(),
hasdomain(integer_list, Xs),
Xs= toTerm(stdio::readLine()),
sum(Xs, Sum),
stdio::write("\n 你输入的整数表 ", Xs,
" \n 其各元素和是:", Sum,"\n\n"), run().
end implement main
goal
mainExe::run(main::run).
一旦我们输入的与预先规定的不符,则程序出错退出。程序运行如图 6.4.3 所示。
-
图 6.4.3 输入整数表并计算表中各元素的和
tostring
我们知道怎么把输入的串转化成 Prolog 的数据类型,但是有时候,我们也需要把 Prolog 内
的类型数据转换成字符串来表达。这时就需要 tostring 谓词了。下面这个例子演示如何把实
数表转换成字符串,并与其他字符串合并,然后显示出来,后面的完全是字符串操作。只是
为了证明已经转换成了字符串。
implement main
open core
clauses
classInfo("main", "toString_example").
class predicates
sum:(integer*, integer) procedure (i, o).
clauses
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
run():- console::init(),
hasdomain(integer_list, Xs),
Xs= toTerm(stdio::readLine()),
sum(Xs, Sum),
stdio::write("\n Your input is a number list : ", Xs,
" \n All the list member's sum is : ", Sum,"\n"),
Str= toString(Xs),
Msg= string::concat("String representation is : ", Str),
stdio::write(Msg),
stdio::nl,
run().
end implement main
goal
mainExe::run(main::run).
可以看到运行结果如图 6.4.4 所示,我们故意把把输入表增加了很多空格,但是一旦表达成
Prolog 的整数表,其格式是固定的。为了证明这个整数表是可以计算的,我们算出了它的元
-
素之和,这是我们之前介绍过的一种运算。这里,如果是定义实数表,则编译时会出问题。
图 6.4.4 表格转换成字符串
format
格式化输出对数据的显示非常有用,下面这个程序,用 format 来构成格式化输出,结果如
图 6.4.5 所示。
implement main
clauses
classInfo("main", "formatting").
run():- console::init(),
Str1= string::format("%8.3f\n %10.1e\n", 3.45678, 35678.0),
Str2= string::format("%d\n %10d\n", 456, 18),
Str3= string::format("%-10d\n %010d\n", 18, 18),
stdio::write(Str1, Str2, Str3),
_=stdio::readline().
end implement main
goal
mainExe::run(main::run).
注意这里用到一些规定:
f 格式化输出固定小数位的实数(如 123.4).
e 用科学记数法表示的实数(如 1.234e+002).
g 用 f 或 e 的最短的格式输出实数
d 带符号的十进制数字
u 无符号的整数
x 十六进制数字
o 十进制
c 字符
B 用 Visual Prolog 二进制类型
R 作为数据库参考数据类型
P 作为程序参数
-
s 作为字符串
程序里的%8.3f 的“%”代表一个参数,f 代表固定位小数格式,“8.3”代表这个小数固定
为 8 个数字位长度,而小数部分占 3 个位长度,你看到时进行了四舍五入。“10.1e”表示科
学记数法表示小数点后部分用 1 位表示,整个数字用 10 个位表示。“%d”、“%10d”输出的
10 进制数字,前者不作位数限制,后者限制用 10 个位置来表达,不足部分前面留空。而
“%-10d”则展示了 10 个位置输出左对齐的情况,“%010d”展示了 10 个位置输出,空余部
分用“0”填满的情况。
图 6.4.5 数据格式化输出展示
第 6.4.1 节 有用的字符串操作谓词
这里我们将介绍几个有用的字符串操作谓词:adjustBehaviour、adjustSide、caseSensitivity,
其定义如下所示:
domains
adjustBehaviour =expand();cutRear();cutOpposite().
adjustSide = left(); right().
caseSensitivity =caseSensitive();caseInsensitive();casePreserve().
predicates
adjust : (string Src, charCount FieldSize, adjustSide Side)
->string AdjustedString.
adjust : (string Src, charCount FieldSize, string Padding,
adjustSide Side) -> string AdjustedString.
adjustLeft : (string Src, charCount FieldSize)
->string AdjustedString.
adjustLeft : (string Src, charCount FieldSize,
string Padding) -> string AdjustedString.
adjustRight : (string Src, charCount FieldSize)
->string AdjustedString.
adjustRight : (string Src, charCount FieldSize,
string Padding) -> string AdjustedString.
adjustRight : (string Src, charCount FieldSize, string Padding,
adjustBehaviour Behaviour) -> string AdjustedString.
它们已经定义在 string 模块里了。我们用下面的程序演示其中一个谓词:
-
implement main
clauses
classInfo("main", "adjust_example").
run():- console::init(),
FstLn1="这是靠右格式,前面有空",
Str1= string::adjust(FstLn1, 16, "◆", string::right),
stdio::write(Str1), stdio::nl,
FstLn2="这是靠左格式,后面有空",
Str2= string::adjust(FstLn2, 16, "◆", string::left),
stdio::write(Str2), stdio::nl,
FstLn10="这是靠右格式,前面有空",
Str10= string::adjust(FstLn10, 16, string::right),
stdio::write(Str10), stdio::nl,
FstLn20="这是靠左格式,后面有空",
Str20= string::adjust(FstLn20, 16, string::left),
stdio::write(Str20), stdio::nl,
Str21= string::adjustright(FstLn20, 16, " "),
stdio::write(Str21), stdio::nl,
Str22= string::adjustleft(FstLn20, 16, " "),
stdio::write(Str22), stdio::nl,
_=stdio::readline().
end implement main
goal
mainExe::run(main::run).
运行结果如图 6.4.6 所示,可以看到,我们可以指定字符串的宽度、对齐方向,并可以指定
空余位置用什么字符串填满。adjust/3 和 adjustleft/3 或者 adjustright/3 的空格填余空相当。
图 6.4.6 对齐和填空
关于字符串连接、分拆、生产的相关谓词,有的我们前面已经提到过,汇总起来有:
concat : (string First, string Last) ->
-
string Output procedure (i,i).
concatList : (core::string_list Source) ->
string Output procedure (i).
concatWithDelimiter : (core::string_list Source,
string Delimiter) -> string Output procedure (i,i).
create : (charCount Length) -> string Output procedure (i).
create : (charCount Length, string Fill) -> string Output procedure (i,i).
createFromCharList : (core::char_list CharList) -> string String.
我们设计一个小程序来演示,如下所示,运行结果如图 6.4.7 所示。
implement main
clauses
classInfo("main", "stringops").
run():-
console::init(),
SndLn= [ "murderous", "doomed",
"that cost the Achaeans countless losses"],
Str= string::concatWithDelimiter(SndLn, ", "),
Rule= string::create(20, "-*&"),
stdio::write("\n",SndLn,"\n",Str), stdio::nl,
stdio::write(Rule), stdio::nl,
_=stdio::readline().
end implement main
goal mainExe::run(main::run).
图 6.4.7
以下这些谓词,都是字符串处理很重要的工具:
equalIgnoreCase : (string First, string Second) determ (i,i).
front : (string Source, charCount Position, string First, string Last)
procedure (i,i,o,o).
frontChar : (string Source, char First, string Last) determ (i,o,o).
frontToken : (string Source, string Token,
string Remainder) determ (i,o,o).
getCharFromValue : (core::unsigned16 Value) -> char Char.
getCharValue : (char Char) -> core::unsigned16 Value.
hasAlpha : (string Source) determ (i).
hasDecimalDigits : (string Source) determ (i).
-
hasPrefix : (string Source, string Prefix, string Rest) determ (i,i,o).
hasSuffix : (string Source, string Suffix, string Rest) determ (i,i,o).
isLowerCase : (string Source) determ (i).
isUpperCase : (string Source) determ (i).
isWhiteSpace : (string Source) determ.
lastChar : (string Source, string First, char Last) determ (i,o,o).
length : (string Source) -> charCount Length procedure (i).
replace : (string Source, string ReplaceWhat,
string ReplaceWith, caseSensitivity Case) ->
string Output procedure.
replaceAll : (string Source, string ReplaceWhat,
string ReplaceWith) -> string Output.
replaceAll : (string Source, string ReplaceWhat,
string ReplaceWith, caseSensitivity Case) ->
string Output.
search : (string Source, string LookFor) ->
charCount Position determ (i,i).
search : (string Source, string LookFor, caseSensitivity Case) ->
charCount Position determ (i,i,i).
split : (string Input, string Separators) -> string_list.
split_delimiter : (string Source, string Delimiter) ->
core::string_list List procedure (i,i).
subString : (string Source, charCount Position,
charCount HowLong) ->
string Output procedure (i,i,i).
toLowerCase : (string Source) ->
string Output procedure (i).
toUpperCase : (string Source) ->
string Output procedure (i).
trim : (string Source) -> string Output procedure (i).
trimFront : (string Source) -> string Output procedure (i).
trimInner : (string Source) -> string Output procedure (i).
trimRear : (string Source) ->string Output procedure (i).
我们举一个小例子如下,其运行结果如图 6.4.8 所示。
implement main
clauses
classInfo("main", "trim_example").
run():-
console::init(),
Str= " I love Visual Prolog. How about you? ",
T= string::trim(Str),
stdio::write(T), stdio::nl,
T1= string::trimInner(T),
stdio::write(T1), stdio::nl,
_=stdio::readline().