prolog语言说明书文档格式.docx
《prolog语言说明书文档格式.docx》由会员分享,可在线阅读,更多相关《prolog语言说明书文档格式.docx(86页珍藏版)》请在冰豆网上搜索。
很遗憾,Prolog在这些方面做得不好,或者说很差。
不过它还是提供了一些基本的方法的。
下面是上述的程序一个完整的例子。
%Thisisthesyntaxforcomments.
%MORTAL-ThefirstillustrativePrologprogram.
mortal(X):
-person(X).
person(plato).
person(aristotle).
mortal_report:
-
write('
Knownmortalsare:
'
),nl,mortal(X),
write(X),nl,
fail.
把这个程序调入Listener中,运行mortal_report.。
-mortal_report.
socrates
plato
aristotle
no
以上程序中的一些函数以后还会详细的介绍的。
最后的那个no表示没有其他的人了。
2.何处获得Prolog
当然是在网络上面!
我现在找到了两个比较好的Prolog版本:
http:
//www.visual-
顾名思义,这是一个可以制作界面的Prolog,有试用版本下载,大概20M左右。
http//
在Amzi的网页上有两种prolog可以下载:
Amzi!
16-bitLogicExplorer,1.xMB,只有Prolog的解释器,完全免费的,速度较慢。
LogicServer,5.xMB,除了有解释器外,还可以进行编译,其最大的功能是能够和其他的编程软件连接,它作为LogicServer,其他的程序(例如使用VB编写的)可以调用它的强大的逻辑推理功能。
这样你所编写的程序会即漂亮又聪明。
90天试用,过期以后不能编译,不过你可以通过修改系统时间,来使用所有功能。
而且作为服务器和其它程序连接,是没有时间限制的。
我更倾向是用Amzi的prolog,因为它的语法比visual-prolog更加简单,而且只要结合VisualBasic,同样可以制作出和visual-prolog相当的程序出来。
3.Amzi!
LogicServer简介
amzi!
logicserver有集成开发环境IDE,你可以在其中编写、调试、运行、编译程序。
它也提供命令行的形式进行运行编译。
它由下面几个部分组成:
Listener
这是prolog解释器,你只需要把源程序调入解释器就可以解释运行了。
源程序的扩展名为.pro。
Complier
Prolog编译器,它的输出是编译过的文件,扩展名为plm,输入就是源程序pro。
Linker
Prolog连接器,输入文件为plm,输出xpl文件,最终的xpl文件就是编译连接以后的最终文件。
注意,这种文件不能够直接运行的。
Runer
Prolog运行器,使用此运行器可以运行xpl文件。
它的速度比直接在Listener中解释运行要快许多。
如果使用集成环境,这些工具都可以使用菜单调用。
下面简单介绍IDE中的Listener的使用。
1选择菜单Listener/Start,打开Amzi!
Listener,出现提示符?
2选择菜单Listener/Consult...,然后选择要解释的Prolog程序,扩展名为pro,其实是文本文件。
3然后就可以输入你的询问了。
VisualBasic+Amzi!
LogicServer
amzi!
中有很详细的例子介绍如何使用VisualBasic调用Prolog。
这里只是简单的介绍一下:
如果你下载了完整的Amzi!
LogicServer,就可以找到这个目录:
samples/vb/hello,你只需要用VB打开hello.vbp,然后运行,就可以看到一个有GreetMe!
按钮的窗口,点击它,就会弹出几个窗口,如果看到GreetingsVisualBasicProgrammer,fromAmzi!
Prolog.的话,就表示一切OK,如果出现找不到hello.xpl文件的错误,你就需要把hello.frm文件打开,找到Command1_Click(),把其中的LoadLS("
hello.xpl"
)改为LoadLS(App.Path+"
\hello.xpl"
),然后再打开hello.vbp重新测试,这次应该没有问题了。
下面简单说明一下这个小系统的构成。
首先你必须有一个已经编译好的prolog文件,扩展名为xpl,在VB中你必须调用它。
由于需要和prologserver打交道,所以系统已经定义好了许多API函数,这些函数放在一个叫做amzi4.bas的文件中,此文件放在include目录下面。
所以如果你自己编写VB的应用程序,不要忘记把amzi4.bas作为一个模块调入你的工程文件。
系统还必须一个叫做amzi4.dll的动态连接库才能够运行,其实amzi4.bas就是这个amzi4.dll的调用窗口。
所有的prolog的功能都在amzi4.dll中,你要么把这个文件copy到windows/system目录下面,要么copy到和你的程序同一个目录下面。
下面再介绍一下调用logicserver的函数。
请参照hello.frm。
CallInitLS("
"
)初始化logicserver,这是第一步,必须要。
LoadLS("
XXX.xpl"
)调入编译好的xpl文件,这一步也是必须的。
CallStrLS(TermAsLong,StrAsString)把字符串Str传给Logicserver运行,就相当于你在解释器中输入的询问一样,返回这次查询的ID到Term,数据类型为Long。
GetArgLS(TermAsLong,ArgNumAsLong,BTypeAsInteger,PtrAsVariant)
这个调用获得Term的某个参数值,ArgNum指出参数的位置,即是第几个参数,BType定义参数的数据类型,Ptr就为这个参数的值。
例如如果调用CallStrLS(Term,"
friend(X,Y)"
).就是对xpl文件进行friend(X,Y).查询,假如查询的结果是:
X=tomY=jack,即查询的结果term是friend(tom,jack),在系统中term其实是一个指针,指向这个真正的项。
然后如果调用GetArgLS(Term,2,bSTR,str)就表示要获得Term的第二个参数的值,这个值用字符串表示,并且放到str中。
所以最后的结果就是str="
jack"
。
CallCloseLS关闭logicserver。
使用CallStrLS和GetArgLS就能够实现在VB和Prolog中交换数据。
当然系统提供的方法远远不止这两个函数,其它的研究就由你来做了。
第二节事实
事实(facts)是prolog中最简单的谓词(predicate)。
它和关系数据库中的记录十分相似。
在下一节中我们会把事实作为数据库来搜索。
谓词:
Prolog语言的基本组成元素,可以是一段程序、一个数据类型或者是一种关系。
它由谓词名和参数组成。
两个名称相同而参数的数目不同的谓词是不同的谓词。
事实的语法结构如下:
pred(arg1,arg2,...argN).
其中pred为谓词的名称。
arg1,...为参数,共有N个。
‘.’是所有的Prolog子句的结束符。
没有参数的谓词形式如下:
pred.
参数可以是以下四种之一:
整数(integer)
绝对值小于某一个数的正数或负数。
原子(atom)
由小写字母开头的字符串。
变量(variable)
由大写字母或下划线(_)开头。
结构(structure)
在以后介绍。
不同的Prolog还增加了一些其他的数据类型,例如浮点数和字符串等。
Prolog字符集包括:
大写字母,A-Z;
小写字母,a-z;
数字,0-9;
+-/\^,.~:
.?
#$等。
原子通常是字母和数字组成,开头的字符必须是小写字母。
例如:
hello
twoWordsTogether
x14
为了方便阅读,可以使用下划线把单词分开。
a_long_atom_name
z_23
下面的是不合法的原子,
no-embedded-hyphens
123nodigitsatbeginning
_nounderscorefirst
Nocapsfirst
使用单引号扩起来的字符集都是合法的原子。
this-hyphen-is-ok'
UpperCase'
embeddedblanks'
下面的由符号组成的也是合法的原子:
-->
++
变量和原子相似,但是开头字符四大写字母或是下划线。
X
Input_List
_4th_argument
Z56
有了这些基本的知识,我们就可以开始编写事实了。
事实通常用来储存程序所需的数据。
例如,某次商业买卖中的顾客数据。
customer/3。
(/3表示customer有三个参数)
customer('
JohnJones'
boston,good_credit).
SallySmith'
chicago,good_credit).
必须使用单引号把顾客名引起来,因为它们是由大写字母开头的,并且中间有空格。
再看一个例子,视窗系统使用事实储存不同的窗口信息。
在这个例子中参数有窗口名称和窗口的位置坐标。
window(main,2,2,20,72).
window(errors,15,40,20,78).
某个医疗专家系统可能有如下的疾病数据库。
disease(plague,infectious).{疾病(瘟疫,有传染性)}
Prolog的解释器提供了动态储存事实和规则的方法,并且也提供了访问它们的方法。
数据库的更新是通过运行‘consult’或‘reconsult’命令。
我们也可以直接在解释器中输入谓词,但是这些谓词不会被储存到硬盘上。
寻找Nani
下面我们正式开始“寻找Nani”游戏的编写。
我们从定义基本的事实开始,这些事实是本游戏的基本的数据库。
它们包括:
房间和它们的联系
物体和它们的位置
物体的属性
玩家在游戏开始时的位置
图2.1“寻找Nani”游戏的的房间格局
首先我们使用room/1谓词定义房间,一共有五条子句,它们都是事实,如图2.1。
room(kitchen).
room(office).
room(hall).
room('
diningroom'
).
room(cellar).
我们使用具有两个参数的谓词来定义物体的位置。
第一个参数代表物体的名称,第二个参数表示物体的位置。
开始时,我们加入如下的物体。
location(desk,office).
location(apple,kitchen).
location(flashlight,desk).
location('
washingmachine'
cellar).
location(nani,'
).
location(broccoli,kitchen).
location(crackers,kitchen).
location(computer,office).
注意:
我们定义的那些符号,例如:
kitchen、desk等对于我们是有意义的,可是它们对于Prolog是没有任何意义的,完全可以使用任何符号来表示房间的名称。
谓词location/2的意思是“第一个参数所代表的物体位于第二个参数所代表的物体中”。
Prolog能够区别location(sink,kitchen)和location(kitchen,sink)。
因此,参数的顺序是我们定义事实时需要考虑的一个重要问题。
下面我们来表达房间的联系。
使用door/2来表示两个房间有门相连,这里遇到了一个小小的困难:
door(office,hall).
我们想要表达的意思是,office和hall之间有一个门。
可是由于Prolog能够区分door(office,hall)和door(hall,office),所以如果我们想要表达一种双向的联系,就必须把每种联系都定义一遍。
door(office,hall).
door(hall,office).
参数的顺序对定义物体的位置有帮助,可是在定义房间的联系时却带来了麻烦。
我们不得不把每个房门都定义两次!
在这一节里,只定义单向的门,以后会很好地解决此问题的。
door(kitchen,office).
door(hall,'
door(kitchen,cellar).
door('
kitchen).
下面定义某些物体的属性,
edible(apple).
edible(crackers).
tastes_yucky(broccoli).
最后,定义手电筒(由于是晚上,玩家必须想找到手电筒,并打开它才能到那些关了灯的房间)的状态和玩家的初始位置。
turned_off(flashlight).
here(kitchen).
好了,到此你应该学会了如何使用Prolog的事实来表达数据了。
第三节简单的查询
1.简单查询
现在我们的游戏中已经有了一些事实,使用Prolog的解释器调入此程序后,我们就可以对这些事实进行查询了。
本章和下一节中的Prolog程序只包括事实,我们要学会如何对这些事实进行查询。
Prolog的查询工作是靠模式匹配完成的。
查询的模板叫做目标(goal)。
如果有某个事实与目标匹配,那么查询就成功了,Prolog的解释器会回显'
yes.'
如果没有匹配的事实,查询就失败了,解释器回显'
no.'
我们把Prolog的模式匹配工作叫做联合(unification)。
当数据库中只包括事实时,以下三个条件是使联合成功的必要条件。
目标谓词名与数据库中的某个谓词名相同。
这两个谓词的参数数目相同。
所有的参数也相同。
在介绍查询之前,让我们回顾一下上一节所编写的Prolog程序。
room(kitchen).
room(office).
location(apple,kitchen).
location(flashlight,desk).
cellar).
location(broccoli,kitchen).
edible(apple).
edible(crackers).
tastes_yucky(broccoli).
here(kitchen).
以上是我们的“寻找Nani”中的所有事实。
把这段程序调入Prolog解释器中后就可以开始进行查询了。
我们的第一个问题是:
office在本游戏中是不是一个房间。
-room(office).{?
-是解释器的提示符}
yes.
Prolog回答yes,因为它在数据库中找到了room(office).这个事实。
我们继续问:
有没有attic这个房间。
-room(attic).
no.
Prolog回答no,因为它在数据库中找不到room(attic).这个事实。
同样我们还可以进行如下的询问。
-location(apple,kitchen).
yes
-location(kitchen,apple).
你看Prolog懂我们的意思呢,它知道苹果在厨房里,并且知道厨房不在苹果里。
但是下面的询问就出问题了。
-door(office,hall).
-door(hall,office).
由于我们定义的门是单方向的,结果遇到了麻烦。
在查询目标中我们还可以使用Prolog的变量。
这种变量和其他语言中的不同。
叫它逻辑变量更合适一点。
变量可以代替目标中的一些参数。
变量给联合操作带来了新的意义。
以前联合操作只有在谓词名和参数都相同时才能成功。
但是引入了变量之后,变量可以和任何的条目匹配。
当联合成功之后,变量的值将和它所匹配的条目的值相同。
这叫做变量的绑定(binding)。
当带变量的目标成功的和数据库中的事实匹配之后,Prolog将返回变量绑定的值。
由于变量可能和多个条目匹配,Prolog允许你察看其他的绑定值。
在每次Prolog的回答后输入“;
”,可以让Prolog继续查询。
下面的例子可以找到所有的房间。
“;
”是用户输入的。
-room(X).
X=kitchen;
X=office;
X=hall;
X='
;
X=cellar;
最后的no表示找不到更多的答案了。
下面我们想看看kitchen中都有些什么。
(变量以大写字母开始)
-location(Thing,kitchen).
Thing=apple;
Thing=broccoli;
Thing=crackers;
我们还可以使用两个变量来查询所有的物体及其位置。
-location(Thing,Place).
Thing=desk
Place=office;
Thing=apple
Place=kitchen;
Thing=flashlight
Place=desk;
...
2.查询的工作原理
当Prolog试图与某一个目标匹配时,例如:
location/2,它就在数据库中搜寻所有用location/2定义的子句,当找到一条与目标匹配时,它就为这条子句作上记号。
当用户需要更多的答案时,它就从那条作了记号的子句开始向下查询。
我们来看一个例子,用户询问:
location(X,kitchen).。
Prolog找到数据库中的第一条location/2子句,并与目标比较。
目标
子句#1
location(X,kitchen)
location(desk,office)
匹配失败,因为第二个参数不同,一个是kitchen,一个是office。
于是Prolog继续比较第二个子句。
子句#2
location(apple,kitchen)
这回匹配成功,而变量X的值就被绑定成了apple。
-location(X,kitchen).
X=apple
如果用户输入分号(;
),Prolog就开始寻找其他的答案。
首先它必须释放(unbinds)变量X。
然后从上一次成功的位置的下一条子句开始继续搜索。
这个过程叫做回溯(backtracking)。
在本例中就是第三条子句。
子句#3
location(flashlight,desk)
匹配失败,直到第六条子句时匹配又成功了。
子句#6
location(broccoli,kitchen)
结果变量X又被绑定为broccoli,解释器显示:
X=broccoli;
再度输入分号,X又被解放,开始新的搜索。
又找到了:
X=crackers;
这回再没有新的子句能够匹配了,于是Prolog回答no,表示最后一次搜索失败了。
要想了解Prolog的运行顺序,最好的方法就是单步调试程序,不过在此之前,还是让我们加深一下对目标的认识吧。
Error!
Unknownswitchargument.
每个端口的功能如下:
1.call开始使用目标搜寻子句。
2.exit目标匹配成功,在成功的子句上作记号,并绑定变量。
3.redo试图重新满足目标,首先释放变量,并从上次的记号开始搜索。
4.fail表示再找不到更多的满足目标的子句了。
下面列出了调试location(X,kitchen).时的情况。