现在我们的游戏中已经有了一些事实,使用Prolog的解释器调入此程序后,我们就可以对这些事实进行查询了。本章和下一章中的Prolog程序只包括事实,我们要学会如何对这些事实进行查询。
Prolog的查询工作是靠模式匹配完成的。查询的模板叫做目标(goal)。如果有某个事实与目标匹配,那么查询就成功了,Prolog的解释器会回显'yes.'。如果没有匹配的事实,查询就失败了,解释器回显'no.'。
我们把Prolog的模式匹配工作叫做联合(unification)。当数据库中只包括事实时,以下三个条件是使联合成功的必要条件:
1. 目标谓词名与数据库中的某个谓词名相同。2. 这两个谓词的参数数目相同。 3. 所有的参数也相同。
在介绍查询之前,让我们回顾一下上一章所编写的Prolog程序。
room(kitchen).room(office).room(hall). room('dining room').room(cellar). door(office, hall).door(kitchen, office).door(hall, 'dining room').door(kitchen, cellar).door('dining room', kitchen). location(desk, office).location(apple, kitchen). location(flashlight, desk). location('washing machine', cellar).location(nani, 'washing machine').location(broccoli, kitchen).location(crackers, kitchen).location(computer, office). 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).no
你看Prolog懂我们的意思呢,它知道苹果在厨房里,并且知道厨房不在苹果里。但是下面的询问就出问题了。
?- door(office, hall). yes
?- door(hall, office).no
由于我们定义的门是单方向的,结果遇到了麻烦。
在查询目标中我们还可以使用Prolog的变量。这种变量和其他语言中的不同。叫它逻辑变量更合适一点。变量可以代替目标中的一些参数。
变量给联合操作带来了新的意义。以前联合操作只有在谓词名和参数都相同时才能成功。但是引入了变量之后,变量可以和任何的条目匹配。
当联合成功之后,变量的值将和它所匹配的条目的值相同。这叫做变量的绑定(binding)。当带变量的目标成功的和数据库中的事实匹配之后,Prolog将返回变量绑定的值。
由于变量可能和多个条目匹配,Prolog允许你察看其他的绑定值。在每次Prolog的回答后输入“;”,可以让Prolog继续查询。下面的例子可以找到所有的房间。“;”是用户输入的。
?- room(X).X = kitchen ;X = office ;X = hall ; X = 'dining room' ;X = cellar ;no
最后的no表示找不到更多的答案了。
下面我们想看看kitchen中都有些什么。(变量以大写字母开始)
?- location(Thing, kitchen).Thing = apple ;Thing = broccoli ; Thing = crackers ;no
我们还可以使用两个变量来查询所有的物体及其位置。
?- location(Thing, Place).Thing = desk Place = office ;
Thing = applePlace = kitchen ;
Thing = flashlight Place = desk ;
...no
查询的工作原理
当Prolog试图与某一个目标匹配时,例如:location/2,它就在数据库中搜寻所有用location/2定义的子句,当找到一条与目标匹配时,它就为这条子句作上记号。当用户需要更多的答案时,它就从那条作了记号的子句开始向下查询。
我们来看一个例子,用户询问:location(X,kitchen).。Prolog找到数据库中的第一条location/2子句,并与目标比较。
目标 | 子句#1 |
location(X, kitchen) | location(desk, office) |
匹配失败,因为第二个参数不同,一个是kitchen,一个是office。于是Prolog继续比较第二个子句。
目标 | 子句#2 |
location(X, kitchen) | location(apple, kitchen) |
这回匹配成功,而变量X的值就被绑定成了apple。
?- location(X, kitchen).X = apple
如果用户输入分号(;),Prolog就开始寻找其他的答案。首先它必须释放(unbinds)变量X。然后从上一次成功的位置的下一条子句开始继续搜索。这个过程叫做回溯(backtracking)。在本例中就是第三条子句。
目标 | 子句#3 |
location(X, kitchen) | location(flashlight, desk) |
匹配失败,直到第六条子句时匹配又成功了 。
目标 | 子句#6 |
location(X, kitchen) | location(broccoli, kitchen) |
结果变量X又被绑定为broccoli,解释器显示:
X = broccoli ;
再度输入分号,X又被解放,开始新的搜索。又找到了:
X = crackers ;
这回再没有新的子句能够匹配了,于是Prolog回答no,表示最后一次搜索失败了。
no
要想了解Prolog的运行顺序,最好的方法就是单步调试程序,不过在此之前,还是让我们加深一下对目标的认识吧。
Prolog的目标有四个端口用来控制运行的流程:调用(call)、退出(exit)、重试(redo)以及失败(fail)。一开始使用Call端口进入目标,如果匹配成功就到了exit端口,如果失败就到了fail端口,如果用户输入分号,则又从redo端口进入目标。下图表示了目标和它的四个端口。
每个端口的功能如下:
- call 开始使用目标搜寻子句。
- exit 目标匹配成功,在成功的子句上作记号,并绑定变量。
- redo 试图重新满足目标,首先释放变量,并从上次的记号开始搜索。
- fail 表示再找不到更多的满足目标的子句了。
下面列出了调试location(X,kitchen).时的情况。括号中的数字表示当前正在考虑的子句。
?- location(X, kitchen).CALL: - location(X, kitchen)EXIT:(2) location(apple, kitchen)X = apple;REDO: location(X, kitchen) EXIT:(6) location(broccoli, kitchen)X = broccoli ;REDO: location(X, kitchen)EXIT:(7) location(crackers, kitchen) X = crackers ; FAIL - location(X, kitchen)no |
在Prolog的解释器中输入,
?- debug.
就可以开始调试你的程序了。