prolog

Prolog(Programming in Logic的缩写)是一种逻辑 编程语言。它建立在 逻辑学的理论基础之上, 最初被运用于 自然语言等研究领域。现在它已广泛的应用在 人工智能的研究中,它可以用来建造专家系统、自然语言理解、智能知识库等。同时它对一些通常的应用程序的编写也很有帮助。使用它能够比其他的语言更快速地开发程序,因为它的编程方法更象是使用逻辑的语言来描述程序。
   历史
  Prolog语言最早由Aix-Marseille大学的Alain Colmerauer与Phillipe Roussel等人于60年代末研究开发。1972年被公认为是Prolog语言正式诞生的年份,自1972年以后,分支出多种Prolog的方言。最主要的两种方言为Edinburgh和Aix-Marseille。最早的Prolog 解释器由Roussel建造,而第一个Prolog 编译器则是 David Warren编写的。
  Prolog一直在北美和欧洲被广泛使用。日本政府曾经为了建造智能计算机而用Prolog来开发ICOT第五代 计算机系统。在早期的机器智能研究领域,Prolog曾经是主要的开发工具。
  80年代 Borland开发的Turbo Prolog,进一步普及了Prolog的使用。1995年确定了 ISO Prolog标准
  目前比较流行的实现工具包括 SWI-Prolog, Yap 等
   prolog的几个特点:
  1. prolog程序没有特定的运行顺序,其运行顺序是由电脑决定的,而不是编程序的人。
  从这个意义上来说,prolog程序不是真正意义上的程序。所谓程序就是按照一定的步骤运行的计算机指令,而prolog程序的运行步骤不由人来决定。它更像一种描述型的语言,用特定的方法描述一个问题,然后由电脑自动找到这个问题的答案。举个极端的例子,你只需要把某个数学题目告诉它,它就会自动的找到答案,而不像使用其他的语言一样,必须人工的编制出某种算法。
  2. prolog程序中没有if、when、case、for这样的控制流程语句
  前面已经说了,程序的运行方式有电脑自己决定,当然就用不到这些控制流程的语句了。通常情况下,程序员不需要了解程序的运行过程,只需要注重程序的描述是否全面,不过prolog也提供了一些控制流程的方法,这些方法和其他语言中的方法有很大的区别,希望你在以后的学习当中能够融会贯通。
  3. prolog程序和数据高度统一
  在prolog程序中,是很难分清楚哪些是程序,哪些是数据的。事实上,prolog中的所有东西都有相同的形式,也就是说数据就是程序,程序就是数据。举一个其他语言的例子:如果想用c语言编写一个计算某个数学表达式的程序很简单(比如:a=2+5*4),因为这是一段程序。但是如果想编写一个计算用户输入的表达式的值的程序就很困难了。因为用户输入的是一段数据(字符串),如果想让c语言处理这个字符串,就需要很多方面的技术。则正是因为在c语言中,程序和数据是分开的。而在prolog就不存在这个问题,你甚至可以很轻松的编写处理其它prolog程序的程序。
  4. prolog程序实际上是一个智能数据库
  prolog的原理就是关系数据库,它是建立在关系数据库的基础上的。在以后的学习中你会发现它和SQL数据库查询语言有很多相似之处。使用prolog可以很方便的处理数据。
  5. 强大的递归功能

  在其它的语言中,你也许已经接触过递归程序了。递归是一种非常简洁的方式,它能够有效的解决许多难题。而在prolog中,递归的功能得到了充分的体现,你甚至都会感到惊奇,递归居然又如此巨大的能力

 

 

 

到百度首页

您查询的关键词是:prolog教程  。如果打开速度慢,可以尝试快速版;如果想保存快照,可以添加到搜藏

(百度和网页http://tieba.baidu.com/f?kz=128101598的作者无关,不对其内容负责。百度快照谨为网络故障时之索引,不代表被搜索网站的即时页面。)


Prolog教程
到百度贴吧首页
新闻    网页    贴吧    知道    MP3    图片    视频    百科
   吧内搜索 | 帮助
 百度贴吧 > prolog 吧 > 浏览贴子吧主:   
  添加到搜藏 | 快速回复 贴吧投诉 
1Prolog教程
Prolog教程1-序
 
 

如果你是一位prolog的新手,希望你首先阅读这篇文章,好对prolog的全局有个了解。在这篇文章中我会把prolog和其他的程序语言做比较,所以希望你已经具有了一定的编程水平。

 

什么是prolog?

prolog是Programming in LOGic的缩写,意思就是使用逻辑的语言编写程序。prolog不是很高深的语言,相反,比较起其他的一些程序语言,例如c、basic等等语言, prolog是更加容易理解的语言。如果你从来没有接触过计算机编程,那么恭喜你,你将很容易的进入prolog世界。如果你已经是其他语言的高手,你就需要完全丢弃你原来的编程思路,否则是很难掌握prolog的。


一个例子

逻辑思维在我们日常生活中比比皆是,prolog正是把这种思维用文字描述出来的计算机语言。还是首先举个例子吧。 

比如一群年轻人正在恋爱,每个人都有自己心中所追求的对象:

张学友爱王菲

张学友爱周慧敏

王菲爱谢廷峰

周慧敏爱张学友

谢廷峰爱王菲

谢廷峰爱周慧敏

刘德华爱周慧敏

......

我们说两个年轻人要互相都喜爱,他们就算是一对情侣,那么上面的谁和谁是情侣呢?

这应该算是一道最简单逻辑推理题目了,那么我们如何用prolog语言实现呢?

“张学友爱王菲”是一条已知的事实,用prolog语言来表达就是:

爱(张学友,王菲). 

注意1:这里是为了阅读方便才使用汉字的,真正的prolog是不允许使用除了基本字符以外字符的,也就是说,上面的句子必须写成love(zhangxueyou,wanfei).,电脑才能够真正的理解。

注意2:最末尾的“.”一定不能掉,它表示一个句子结束。

注意3:上面词汇对于电脑来说并没有真正的含义,所以我们完全可以用 ai(zxy,wf).来表达这个关系,更进一步,我们甚至可以用 xxx(a,b).来表达,只要你自己心里清楚xxx表示爱,a表示张学友,b表示王菲就可以了。

注意4:张学友和王菲的顺序也没有特别的规定,你完全可以把他们换个位置:爱(王菲,张学友). 只要你心里清楚它表达的意思就行了,而以后都遵循这种被爱的人在前面的顺序,就不会出错。

其他的事实我就不写了,你可以参照上面的例子自己把已知事实翻译成prolog的语句。

那么情侣的概念怎么定义呢?也很简单!

情侣(某人甲,某人乙):-爱(某人甲,某人乙),爱(某人乙,某人甲).

:-在prolog中表示“如果”的意思,我们使用它来定义规则。上面这句话的意思就是,某人甲和某人乙是情侣的规则就是:某人甲爱某人乙,并且某人乙爱某人甲。上面用来分隔两个爱的句子的“,”表示并且的意思。

当然为了能够让电脑运行,这个句子要改为英文的:

lovers(X,Y):-love(X,Y),love(Y,X). 

注意:在prolog中以小写字符开头的字符串代表确知的事物,比如love表示爱这种关系,而zhangxueyou表示张学友。而以大写字母开头的字符串表示未确定的事物,翻译成汉语就是某某。

完整的可运行的prolog程序如下:(我的拼音不好,要是什么人的名字拼写错了,请原谅:)

love(zhangxueyou,wanfei).
love(zhangxueyou,zouhuimin).
love(wanfei,xietinfen).
love(zouhuimin,zhangxueyou).
love(xietinfen,wanfei).
love(xietinfen,zouhuimin).
love(liudehua,zouhuimin).
lovers(X,Y):-love(X,Y),love(Y,X).

我们可以看出来,完整的prolog程序是有事实和规则组成的。事实用来储存一些数据,而规则用来储存某种可以推理出来的关系。

如果把上面的程序调入prolog解释器(关于prolog解释器,在后面有介绍)然后就可以对以上的程序进行询问。

prolog解释器的提示符号为“?-”,你只需要在在这个提示符后面输入自己的句子就可以了。让我们来看第一个询问:

?-love(zhangxueyou,wanfei).

事实上我们的询问完全和程序中的第一条事实一样,这个询问是“是非”询问,也就是说电脑回答的答案是yes或者no。上面的询问的含义是:就你所知,张学友爱王菲么?由于我们的程序中间有这样的事实,所以解释器将回答。
作者: NOD·AID
   
2006-8-27 10:09   回复此发言  

2Prolog教程

yes.

如果我们问:

?-love(zhangxueyou,liudehua). 

解释器将回答

no.

因为它没有发现love(zhangxueyou,liudehua).这个事实。

在询问中我们可以使用大写字母代表未知的事物,让解释器找到答案。例如:

?-love(zhangxueyou,X).

这句话询问的是:张学友都喜欢那些人。解释器将给出答案:

X=wanfei;
X=zouhuimin;
no.

注意1:上面的两个“;”是人工输入的,当解释器找到一个答案之后,它将这个答案输出,并且等待用户的进一步输入,如果用户输入“;”,解释器将继续寻找其他的答案,如果输入的是别的符号,解释器将终止查询。

最后那个no.是因为,系统在输出了zouhuimin这个答案以后,用户输入“;”,表示还想知道其他的答案,而解释器又找不到其他的答案了,于是输出no.来终止查询。我们再看一个例子:

?-love(X,zouhuimin).
X=zhangxueyou;
X=xietinfen;
X=liudehua;
no.

在上面的询问中,我们只涉及到对事实的查询,下面我们来看规则的用法。

?- lovers(X,Y).
X = zhangxueyou 
Y = zouhuimin ;

X = wanfei
Y = xietinfen ;

X = zouhuimin
Y = zhangxueyou ;

X = xietinfen
Y = wanfei ;

no

我们看到lovers(X,Y).找出了系统中所有的恋人。不过每对恋人被显示了两次,这是因为prolog是考虑顺序的,也就是说lovers(a,b).和lovers(b,a).并不等价。这一点在后面的学习中,你会了解。

再看一个例子:

?- lovers(wanfei,Y). 
Y = xietinfen ;
no

询问王菲的恋人,结果是xietinfen。呵呵,还挺聪明的。我们看到同样是lovers,根据其参数不同,功能也不同,这也是prolog的一个大特点。

最后让我们编写一个寻找情敌的规则来结束这一节内容吧。

rival_in_love(X,Y):-love(X,Z),not(love(Z,X)),love(Z,Y).

这段程序可以理解为:Y是X的情敌的条件是:X喜欢Z(代表某个人),而Z不喜欢X,而Y是Z喜欢的人。哈哈,这不正是情敌的条件嘛。

?- rival_in_love(X,Y). 
X = zhangxueyou
Y = xietinfen ;

X = xietinfen
Y = zhangxueyou ;

X = liudehua
Y = zhangxueyou ; 

no

好了,你自己分析一下为什么会是这样的答案吧。


为什么要prolog

看完上面的例子,不知道是否提起了你对prolog的兴趣。如果你感兴趣的话,那么让我们继续来看prolog能够做一些什么事情吧。

理论上来说使用c语言可以编制任何种类的程序,甚至连prolog语言都是使用c语言编写的。不过对于急于开发应用程序的用户,最关心的是如何最经济最有效率的开发程序,prolog为你多提供了一个选择的余地。

prolog很适合于开发有关人工智能方面的程序,例如:专家系统、自然语言理解、定理证明以及许多智力游戏。曾经有人预言prolog将成为下一代计算机的主要语言,虽然这个梦想目前还很难实现,不过世界上已经有许多prolog的应用实例了。你要坚信,它绝对不是那种只在实验室发挥作用的语言,之所以大多数人都不了解它,是因为它的应用范围比较特殊而已。

prolog有许多不足之处,但是这并不影响它在逻辑推理方面的强大功能,不过最好的方法是使用某种一般语言和prolog结合,一般语言完成计算、界面之类的操作,而prolog则专心实现逻辑运算的操作。例如:你编写一个下棋程序,用prolog来让电脑思考如何下棋,而用Visual Basic来编写界面。我们将在以后介绍这方面的技术。

总之,prolog在许多方面将极大的减少你的编程负担,所以赶快来了解一下它吧, 也许你日后遇到什么难题,可以使用prolog迎刃而解,到那个时候,你就知道今天的学习没有白费了。


prolog的特点

我个人总结了prolog的以下几个特点,因为叫做特点,所以自然要和其他的语言进行比较。

1. prolog程序没有特定的运行顺序,其运行顺序是由电脑决定的,而不是编程序的人。
作者: NOD·AID
   
2006-8-27 10:09   回复此发言  

3Prolog教程
从这个意义上来说,prolog程序不是真正意义上的程序。所谓程序就是按照一定的步骤运行的计算机指令,而prolog程序的运行步骤不由人来决定。它更像一种描述型的语言,用特定的方法描述一个问题,然后由电脑自动找到这个问题的答案。举个极端的例子,你只需要把某个数学题目告诉它,它就会自动的找到答案,而不像使用其他的语言一样,必须人工的编制出某种算法。 

2. prolog程序中没有if、when、case、for这样的控制流程语句
前面已经说了,程序的运行方式有电脑自己决定,当然就用不到这些控制流程的语句了。通常情况下,程序员不需要了解程序的运行过程,只需要注重程序的描述是否全面,不过prolog也提供了一些控制流程的方法,这些方法和其他语言中的方法有很大的区别,希望你在以后的学习当中能够融会贯通。

3. prolog程序和数据高度统一
在prolog程序中,是很难分清楚哪些是程序,哪些是数据的。事实上,prolog中的所有东西都有相同的形式,也就是说数据就是程序,程序就是数据。举一个其他语言的例子:如果想用c语言编写一个计算某个数学表达式的程序很简单(比如:a=2+54),因为这是一段程序。但是如果想编写一个计算用户输入的表达式的值的程序就很困难了。因为用户输入的是一段数据(字符串),如果想让c语言处理这个字符串,就需要很多方面的技术。则正是因为在c语言中,程序和数据是分开的。而在prolog就不存在这个问题,你甚至可以很轻松的编写处理其它prolog程序的程序。

4. prolog程序实际上是一个智能数据库
prolog的原理就是关系数据库,它是建立在关系数据库的基础上的。在以后的学习中你会发现它和SQL数据库查询语言有很多相似之处。使用prolog可以很方便的处理数据。

5. 强大的递归功能
在其它的语言中,你也许已经接触过递归程序了。递归是一种非常简洁的方式,它能够有效的解决许多难题。而在prolog中,递归的功能得到了充分的体现,你甚至都会感到惊奇,递归居然又如此巨大的能力。

下一步该怎么做

如果你决定下来要学习prolog了,那么请继续看这里的教程。你要注意哦,这里是目前全球唯一的详细介绍prolog的中文网站。

1. 在学习之前,希望你能够搞到比较好的prolog解释器,下一节我将就解释器进行一些讨论。

2. 然后你必须熟练的掌握解释器的使用方法。

3. 然后就可以开始阅读我的教程了。

4. 当你学习完整个教程以后,希望你能够进入人工智能实例环节,那里有更多的、更有用的prolog编程方法,和有趣的程序。

5. 如果你想使用prolog和其它的语言结合起来,编写让人瞠目结舌的又聪明、又漂亮的程序,你就应该仔细研究VB+prolog这一节。

好了,还等什么? 让我们开始吧。 
 
作者: NOD·AID
   
2006-8-27 10:09   回复此发言  

4回复:Prolog教程
Prolog教程2-入门 
探索Prolog

Prolog在英语中的意思就是Programming in LOGic(逻辑编程)。它是建立在逻辑学的理论基础之上的, 最初是运用于自然语言的研究领域。然而现在它被广泛的应用在人工智能的研究中,它可以用来建造专家系统、自然语言理解、智能知识库等。同时它对一些通常的应用程序的编写也很有帮助。使用它能够比其他的语言更快速地开发程序,因为它的编程方法更象是使用逻辑的语言来描述程序。

从纯理论的角度来讲,Prolog是一种令人陶醉的编程语言,但是在这本书中还是着重介绍他的实际使用方法。


进入Prolog世界

和其他的语言一样,最好的学习方法是实践。这本书将使用Prolog的解释器来向大家介绍几个具体的应用程序的编写过程。 

首先你应该拥有一个Prolog的解释器,你可以在免费prolog版本中找到它。关于解释器的使用,请参阅相关的使用说明文档,建议使用amzi prolog 或者swi prolog来运行本网站的程序。

 

逻辑编程

什么叫逻辑编程?也许你还没有一个整体的印象,还是让我们首先来研究一个简单的例子吧。运用经典的逻辑理论,我们可以说“所有的人(person)都属于人类(moral)”,如果用Prolog的语言来说就是“对于所有的X,只要X是一个人,它就属于人类。”

moral(X):-person(X). 

同样,我们还可以加入一些简单的事实,比如:苏格拉底(socrates)是一个人。

person(socrates).

有了这两条逻辑声明,Prolog就可以判断苏格拉底是不是属于人类。在Prolog的Listener中键入如下的命令:

?-mortal(socrates). (此句中的'?-'是Listener的提示符,本句表示询问苏格拉底是不是属于人类。)

Linstener将给出答案:

yes

我们还可以询问,“谁属于人类?”

?-mortal(X).

我们会得到如下的答案:

X= socrates

这个简单的例子显示了Prolog的一些强大的功能。它能让程序代码更简洁、更容易编写。在多数情况下Prolog的程序员不需要关心程序的运行流程,这些都由Prolog自动地完成了。

当然,一个完整的程序不能只包括逻辑运算部分,还必须拥有输入输出,乃至用户界面部分。很遗憾,Prolog在这些方面做得不好,或者说很差。不过它还是提供了一些基本的方法的。下面是上述的程序一个完整的例子。

 
 % This is the syntax for comments. % MORTAL - The first illustrative Prolog 
program mortal(X) :- person(X).
person(socrates).
person(plato).
person(aristotle).
mortal_report:-
write('Known mortals are:'),nl, mortal(X), write(X),nl,
fail. 
 

 
把这个程序调入Listener中,运行mortal_report.。

?- mortal_report. 
Known mortals are:
socrates
plato
aristotle
no 

以上程序中的一些函数以后还会详细的介绍的。最后的那个no表示没有其他的人了。


进入下一章

从下一章起,就开始正式介绍Prolog的编程方法了。我将用一个实例来介绍Prolog,这是一个文字的冒险游戏,你所扮演的角色是一个三岁的小女孩,你想睡觉了,可是没有毛毯(nani)你就不能安心的睡觉。所以你必须在那个大房子中找到你的毛毯,这就是你的任务。这个游戏能够显示出一些Prolog的独到之处,不过Prolog的功能远不止编个简单的游戏,所以文中还将介绍一些其他的小程序。
作者: NOD·AID
   
2006-8-27 10:09   回复此发言  

5回复:Prolog教程
Prolog教程3-事实 
事实 (facts) 
注:斜粗体字表示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 
Nocapsfirst 
下划线不能放在最前面

使用单引号扩起来的字符集都是合法的原子。例如:

'this-hyphen-is-ok'
'UpperCase'
'embedded blanks'

下面的由符号组成的也是合法的原子:


>,++ 

变量和原子相似, 但是开头字符四大写字母或是下划线。例如:


Input_List
下划线开头的都是变量
Z56 

有了这些基本的知识,我们就可以开始编写事实了。事实通常用来储存程序所需的数据。例如,某次商业买卖中的顾客数据。customer/3。(/3表示customer有三个参数)

customer('John Jones', boston, good_credit). 
customer('Sally Smith', chicago, good_credit).

必须使用单引号把顾客名引起来,因为它们是由大写字母开头的,并且中间有空格。

再看一个例子,视窗系统使用事实储存不同的窗口信息。在这个例子中参数有窗口名称和窗口的位置坐标。

window(main, 2, 2, 20, 72). 
window(errors, 15, 40, 20, 78). 

某个医疗专家系统可能有如下的疾病数据库。

disease(plague, infectious). {疾病(瘟疫,有传染性)}

Prolog的解释器提供了动态储存事实和规则的方法,并且也提供了访问它们的方法。数据库的更新是通过运行‘consult’或‘reconsult’命令。我们也可以直接在解释器中输入谓词,但是这些谓词不会被储存到硬盘上。


寻找Nani

下面我们正式开始“寻找Nani”游戏的编写。我们从定义基本的事实开始,这些事实是本游戏的基本的数据库。它们包括:

房间和它们的联系 
物体和它们的位置 
物体的属性 
玩家在游戏开始时的位置 


“寻找Nani”游戏的的房间格局

首先我们使用room/1谓词定义房间,一共有五条子句,它们都是事实,如图2.1。

room(kitchen). 
room(office). 
room(hall). 
room('dining room'). 
room(cellar). 

我们使用具有两个参数的谓词来定义物体的位置。第一个参数代表物体的名称,第二个参数表示物体的位置。开始时,我们加入如下的物体。

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).

注意:我们定义的那些符号,例如: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(office, hall). 
door(kitchen, office).
door(hall, 'dining room'). 
door(kitchen, cellar).
door('dining room', kitchen).

下面定义某些物体的属性,

edible(apple). 
edible(crackers). 

tastes_yucky(broccoli). 

最后,定义手电筒(由于是晚上,玩家必须想找到手电筒,并打开它才能到那些关了灯的房间)的状态和玩家的初始位置。

turned_off(flashlight).
here(kitchen).

好了,到此你应该学会了如何使用Prolog的事实来表达数据了。
作者: NOD·AID
   
2006-8-27 10:10   回复此发言  

6回复:Prolog教程
Prolog教程4-简单查询 
现在我们的游戏中已经有了一些事实,使用Prolog的解释器调入此程序后,我们就可以对这些事实进行查询了。本章和下一章中的Prolog程序只包括事实,我们要学会如何对这些事实进行查询。

Prolog的查询工作是靠模式匹配完成的。查询的模板叫做目标(goal)。如果有某个事实与目标匹配,那么查询就成功了,Prolog的解释器会回显'yes.'。如果没有匹配的事实,查询就失败了,解释器回显'no.'。

我们把Prolog的模式匹配工作叫做联合(unification)。当数据库中只包括事实时,以下三个条件是使联合成功的必要条件。

目标谓词名与数据库中的某个谓词名相同。
这两个谓词的参数数目相同。 
所有的参数也相同。

在介绍查询之前,让我们回顾一下上一章所编写的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 = apple
Place = kitchen ;

Thing = flashlight 
Place = desk ;


...
no 


查询的工作原理

当Prolog试图与某一个目标匹配时,例如:location/2,它就在数据库中搜寻所有用location/2定义的子句,当找到一条与目标匹配时,它就为这条子句作上记号。当用户需要更多的答案时,它就从那条作了记号的子句开始向下查询。
作者: NOD·AID
   
2006-8-27 10:10   回复此发言  

7回复:Prolog教程

我们来看一个例子,用户询问:location(X,kitchen).。Prolog找到数据库中的第一条location/2子句,并与目标比较。

目标 location(X, kitchen) 
子句#1 location(desk, office) 

匹配失败,因为第二个参数不同,一个是kitchen,一个是office。于是Prolog继续比较第二个子句。

目标 location(X, kitchen)
子句#2 location(apple, kitchen) 

这回匹配成功,而变量X的值就被绑定成了apple。

?- location(X, kitchen).
X = apple 

如果用户输入分号(;),Prolog就开始寻找其他的答案。首先它必须释放(unbinds)变量X。然后从上一次成功的位置的下一条子句开始继续搜索。这个过程叫做回溯(backtracking)。在本例中就是第三条子句。

目标 location(X, kitchen)
子句#3 location(flashlight, desk) 

匹配失败,直到第六条子句时匹配又成功了 。

目标 location(X, kitchen) 
子句#6 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. 

就可以开始调试你的程序了。

在下一章中将介绍更为复杂的查询。
作者: NOD·AID
   
2006-8-27 10:10   回复此发言  

8回复:Prolog教程
Prolog教程5-混合查询 
我们可以把简单的查询连接起来,组成一些较复杂的查询。例如,如果我们想知道厨房里能吃的东西,就可以向Prolog进行如下的询问。

?- location(X, kitchen), edible(X). 

简单的查询只有一个目标,而混合查询可以把这些目标连接起来,从而进行较为复杂的查询。上面的连接符号','是并且的意思。

上面的式子用语言来描述就是“寻找满足条件的X,条件是:X在厨房里,并且X能吃。”如果某个变量在询问中多次出现,则此变量在所有出现的位置都必须绑定为相同的值。所以上面的查询只有找到某一个X的值,使得两个目标都成立时,才算查询成功。

每次查询所使用的变量都是局部的变量,它只在本查询中有意义,所以当我们进行了如下的查询后,

?- location(X, kitchen), edible(X).
X = apple ;
X = crackers ; 
no

查询结果中没有broccoli(椰菜),因为我们没有把它定义为可吃的东西。此后,还可以用X进行其他的查询。

?- room(X).
X = kitchen ;
X = office ;
X = hall ; 
...;
no 

除了使用逻辑的方法理解混合查询外,还可以通过分析程序的运行步骤来理解。用程序的语言来说就是“首先找到一样位于厨房的东西,然后判断它能否食用,如果不能,就到厨房里找下一样东西,再判断能否食用。一直如此重复,直到找到答案或把厨房的东西全部查完为止。”

请参照下图来理解。

调用查询后,程序将按照下面的步骤运行,请参照上图来理解。

搜索第一个目标,如果成功转到2,如果失败则回答'no',查询结束。 
搜索第二个目标,如果成功转到3,如果失败转到1。 
把绑定的变量的值输出。用户输入';'后转到2。 
上面的例子中只有一个变量,下面我们再来看一个有两个变量的例子。

?- door(kitchen, R), location(T,R).
R = office
T = desk ;

R = office
T = computer ;

R = cellar
T = 'washing machine' ; 

no 

上面的查询用逻辑的语言来解释就是:“找房间R,使得从厨房到房间R有门相连,并且把房间R中的物品T(这里是房间R的所有物品)也找出来。”

下面是此查询的单步运行过程。

Goal: door(kitchen, R), location(T,R)

1 CALL door(kitchen, R)
1 EXIT (2) door(kitchen, office)
2 CALL location(T, office)
2 EXIT (1) location(desk, office)
R = office 
T = desk ;


2 REDO location(T, office)
2 EXIT (8) location(computer, office)
R = office
T = computer ;


2 REDO location(T, office)
2 FAIL location(T, office)
1 REDO door(kitchen, R) 
1 EXIT (4) door(kitchen, cellar)
2 CALL location(T, cellar)
2 EXIT (4) location('washing machine', cellar)
R = cellar 
T = 'washing machine' ; 


2 REDO location(T, cellar)
2 FAIL location(T, cellar)
1 REDO door(kitchen, R) 
1 FAIL door(kitchen, R)
no 

 

内部谓词

讲了这么多了,我们还只是用到了Prolog的一些语法,完全没有使用Prolog提供的一些内部的函数,我把这些内部函数称为内部谓词。和其他的程序语言一样,Prolog也提供了一些基本的输入输出函数,下面我们要编写一个较复杂的查询,它能够找到所有厨房里能够吃的东西,并把它们列出来。而不是像以前那样需要人工输入';'。 

要想完成上面的任务,我们首先必须了解内部谓词的概念。内部谓词是指已经在Prolog中事先定义好的谓词。在内存中的动态数据库中是没有内部谓词的子句的。当解释器遇到了内部谓词的目标,它就直接调用事先编好的程序。

内部谓词一般所完成的工作都是与逻辑程序无关的,例如输入输出的谓词。所以我们可以把这些谓词叫做非逻辑谓词。

但是这些谓词也可以作为Prolog的目标,所以它们也必须拥有和逻辑谓词相同的四个端口:Call、Fail、Redo和Exit。
作者: NOD·AID
   
2006-8-27 10:10   回复此发言  

9回复:Prolog教程

下面介绍几个常用的输出谓词。

write/1
此谓词被调用时永远是成功的,并且它可以把它的参数作为字符串输出到屏幕上。当回溯时,它永远是失败,所以回溯是不会把已经写到屏幕上的字符又给删除的。

nl/0
此谓词没有参数,和write一样,从Call端口调用时总是成功的,从Redo端口回溯时总是失败的,它的作用是在屏幕上输出一个回车符。

tab/1
此谓词的参数是一个整数,它的作用是输出n个空格,n为它的参数。其控制流程与上面两个相同。

下图是一般情况下的Prolog目标的内部流程控制示意图。我们将使用此图和内部谓词的流程控制图相比较。 

上图中左上角的菱形方块表示从Call端口进入目标时所进行的处理。它从某谓词的第一个子句开始搜索,如果匹配成功就到Exit端口,如果没有找到任何一个子句与目标匹配就转到Fail端口。

右下角的方块表示从Redo端口进入目标时所进行的处理,从最近一次成功的子句开始向下搜索,如果匹配成功就转到Exit端口,如果没有找个更多的子句满足目标就转到Fail端口。

I/O谓词的流程控制和上述的不同,它不会改变流程的方向,如果流程从它的左边进入,就会从它的右边流出;而如果从它的右边进入,则会从它的左边流出。请参考下图理解。

I/O谓词不会改变变量的值,但是它们可以把变量的值输出。

还有一个专门引起回溯的内部谓词fail/0,从它的名字不难看出,它的调用永远是失败的。如果fail/0从左边得到控制权,则它立即把控制权再传回到左边。它不会从右边得到控制,因为没法通过fail/0把控制权传到右侧。它的内部流程控制如下:

以前我们是靠使用';'来进入目标的Redo端口的,并且变量的值的输出是靠解释器完成的。现在有了上面几个内部谓词,我们就可以靠I/O谓词来显示变量的值,靠fail谓词来引起自动的回溯。

下面是此查询语句及其运行结果。

?- location(X, kitchen), write(X) ,nl, fail.
apple 
broccoli
crackers 
no 

下面是此查询的流程图。

下面是此查询的单步调试过程。

Goal: location(X, kitchen), write(X), nl, fail.
1 CALL location(X, kitchen)
1 EXIT (2) location(apple, kitchen) 
2 CALL write(apple)
apple
2 EXIT write(apple) 
3 CALL nl 

3 EXIT nl 
4 CALL fail 
4 FAIL fail
3 REDO nl
3 FAIL nl 
2 REDO write(apple) 
2 FAIL write(apple)
1 REDO location(X, kitchen)
1 EXIT (6) location(broccoli, kitchen)
2 CALL write(broccoli)
broccoli
2 EXIT write(broccoli)
3 CALL nl

3 EXIT nl
4 CALL fail
4 FAIL fail
3 REDO nl
3 FAIL nl
2 REDO write(broccoli)
2 FAIL write(broccoli)
1 REDO location(X, kitchen)
1 EXIT (7) location(crackers, kitchen)
2 CALL write(crackers) crackers
2 EXIT write(crackers) 
3 CALL nl 

3 EXIT nl
4 CALL fail
4 FAIL fail
3 REDO nl
3 FAIL nl 
2 REDO write(crackers) 
2 FAIL write(crackers)
1 REDO location(X, kitchen) 
1 FAIL location(X, kitchen)
no 


下面请你分析一下,

?- door(kitchen, R), write(R), nl, location(T,R), tab(3), write(T), nl, fail. 

的输出的结果是什么呢?
作者: NOD·AID
   
2006-8-27 10:10   回复此发言  

10回复:Prolog教程
Prolog教程6-规则 
前面我们已经说过,谓词是使用一系列的子句来定义的。以前我们所学习的子句是事实,现在让我们来看看规则吧。规则的实质就是储存起来的查询。它的语法如下:

head :- body

其中,

head 是谓词的定义部分,与事实一样,也包括谓词名和谓词的参数说明。
:- 连接符,一般可以读作‘如果’。
body 一个或多个目标,与查询相同。

举个例子,上一章中的混合查询--找到能吃的东西和它所在的房间,可以使用如下的规则保存,规则名为where_food/2。

where_food(X,Y) :- location(X,Y), edible(X). 

用语言来描述就是“在房间Y中有可食物X的条件是:X在Y房间中,并且X可食。”

我们现在可以直接使用此规则来找到房间中可食的物品。

?- where_food(X, kitchen). 
X = apple ;
X = crackers ;
no 

?- where_food(Thing, 'dining room').
no 

它也可以用来判断,

?- where_food(apple, kitchen).
yes 

或者通过它找出所有的可食物及其位置,

?- where_food(Thing, Room). 
Thing = apple
Room = kitchen ; 

Thing = crackers
Room = kitchen ;
no 

我们可以使用多个事实来定义一个谓词,同样我们也可以用多个规则来定义一个谓词。例如,如果想让Prolog知道broccoli(椰菜)也是可食物,我们可以如下定义where_food/2规则。

where_food(X,Y) :- location(X,Y), edible(X).
where_food(X,Y) :- location(X,Y), tastes_yucky(X).

在以前的事实中我们没有把broccoli定义为edible,即没有edible(broccoli).这个事实,所以单靠where_food的第一个子句是不能找出broccoli的,但是我们曾在事实中定义过:tastes_yucky(broccoli).{不好吃(椰菜).},所以如果加入第二个子句,Prolog就可以知道broccoli也是food(食物)了。下面是它的运行结果。

?- where_food(X, kitchen). 
X = apple ; 
X = crackers ;
X = broccoli ;
no 


规则的工作原理

到现在为止,我们所知道的Prolog所搜索的子句只有事实。下面我们来看看Prolog是如何搜索规则的。

首先,Prolog将把目标和规则的子句的头部(head)进行匹配,如果匹配成功,Prolog就把此规则的body部分作为新的目标进行搜索。

实际上规则就是多层的询问。第一层由原始的目标组成,从下一层开始就是由与第一层的目标相匹配的规则的Body中的子目标组成。(这句话有点难理解,请参照下面图来分析)

每一层还可以有子目标,理论上来讲,这种目标的嵌套可以是无穷的。但是由于计算机的硬件限制,子目标只可能有有限次嵌套。

下图显示了这种目标嵌套的流程图,请你注意第一层的第三个目标是如何把控制权回溯到第二层的子目标中的。

在这个例子中,第一层的中间的那个目标的结果依赖于第二层的目标的结果。此目标会把程序的控制权传给他的子目标。

下面我们详细地分析一下Prolog在匹配有规则的子句时是如何工作的。请注意用‘-’分隔的两个数字,第一个数字代表当前的目标级数,第二个数字代表当前目标层中正在匹配的目标的序号。例如:

2-1 EXIT (7) location(crackers, kitchen) 

表示第二层的第一个目标的EXIT过程。

我们的询问如下
?- where_food(X, kitchen).
首先我们寻找有where_food/2的子句. 
1-1 CALL where_food(X, kitchen) 
与第一个子句的头匹配 
1-1 try (1) where_food(X, kitchen) ;第一个where_food/2的子句与目标匹配。
于是第一个子句的Body将变为新的目标。 
2-1 CALL location(X, kitchen) 
从现在起的运行过程就和我们以前一样了。 
2-1 EXIT (2) location(apple, kitchen)
2-2 CALL edible(apple)
2-2 EXIT (1) edible(apple) 
由于Body的所有目标都成功了,所以第一层的目标也就成功了。 
1-1 EXIT (1) where_food(apple, kitchen) 
作者: NOD·AID
   
2006-8-27 10:11   回复此发言  

11回复:Prolog教程
X = apple ; 
第一层的回溯过程使得又重新进入了第二层的目标。 
1-1 REDO where_food(X, kitchen) 
2-2 REDO edible(apple)
2-2 FAIL edible(apple) 
2-1 REDO location(X, kitchen)
2-1 EXIT (6) location(broccoli, kitchen) 
2-2 CALL edible(broccoli)
2-2 FAIL edible(broccoli)
2-1 REDO location(X, kitchen)
2-1 EXIT (7) location(crackers, kitchen)
2-2 CALL edible(crackers) 
2-2 EXIT (2) edible(crackers)
1-1 EXIT (1) where_food(crackers, kitchen)
X = crackers ; 
下面就没有更多的答案了,于是第一层的目标失败。 
2-2 REDO edible(crackers) 
2-2 FAIL edible(crackers)
2-1 REDO location(X, kitchen) 
2-1 FAIL location(X, kitchen)

下面Prolog开始寻找另外的子句,看看它们的头部(head)能否与目标匹配。在此例中,where_food/2的第二个子句也可以与询问匹配。 
1-1 REDO where_food(X, kitchen) 
Prolog又开始试图匹配第二个子句的Body中的目标。 
1-1 try (2) where_food(X, kitchen) ;第二个where_food/2的子句与目标匹配。
下面将找到不好吃的椰菜。即 tastes_yucky 的 broccoli.
2-1 CALL location(X, kitchen) 
2-1 EXIT (2) location(apple, kitchen) 
2-2 CALL tastes_yucky(apple) 
2-2 FAIL tastes_yucky(apple) 
2-1 REDO location(X, kitchen) 
2-1 EXIT (6) location(broccoli, kitchen) 
2-2 CALL tastes_yucky(broccoli)
2-2 EXIT (1) tastes_yucky(broccoli) 
1-1 EXIT (2) where_food(broccoli, kitchen) 
X = broccoli ; 
回溯过程将让Prolog寻找另外的where_food/2的子句。但是,这次它没有找到。
2-2 REDO tastes_yucky(broccoli) 
2-2 FAIL tastes_yucky(broccoli) 
2-1 REDO location(X,kitchen) 
2-1 EXIT (7) location(crackers, kitchen) 
2-2 CALL tastes_yucky(crackers) 
2-2 FAIL tastes_yucky(crackers) 
2-2 REDO location(X, kitchen) 
2-2 FAIL location(X, kitchen) 
1-1 REDO where_food(X, kitchen) ;没有找到更多的where_food/2的子句了。
1-1 FAIL where_food(X, kitchen)
no 


在询问的不同层的目标中,即是相同的变量名称也是不同的变量,因为它们都是局部变量。这于其他语言中的局部变量是差不多的。

我们再来分析一下上面的那个例子吧。

where_food(X,Y) :- location(X,Y), edible(X). 

查询的目标是:

?- where_food(X1, kitchen) 

第一个子句的头是:

where_food(X2, Y2) 

目标和子句的头部匹配,在Prolog中如果变量和原子匹配,那么变量就绑定为此原子的值。如果两个变量进行了匹配,那么这两个变量将同时绑定为一个内部变量。此后,这两个变量中只要有一个绑定为了某个原子的值,另外一个变量也会同时绑定为此值。所以上面的匹配操作将有如下的绑定。

X1 = _01 ;01为Prolog的内部变量。
X2 = _01
Y2 = kitchen

于是当上述的匹配操作完成后,规则where_food/2的body将变成如下的查询:

location(_01, kitchen), edible(_01).

当内部变量取某值时,例如'apple',X1和X2将同时绑定为此值,这是Prolog变量和其他语言的变量的基本的区别。如果你学过C语言,容易看出,实际上X1和X2都是指针变量,当它们没有绑定值时,它们的值为NULL,一旦绑定,它们就会指向某个具体的位置,上例中它们同时指向了_01这个变量,其实_01变量还是个指针,直到最后某个指针指向了具体的值,那么所有的指针变量就都被绑定成了此值。

使用规则

使用规则我们可以很容易的解决单向门的问题。我们可以再定义有两个子句的谓词来描述这种双向的联系。此谓词为connect/2。

connect(X,Y) :- door(X,Y). 
connect(X,Y) :- door(Y,X).

它代表的意思是“房间X和Y相连的条件是:从X到Y有扇门,或者从Y到X有扇门"。请注意此处的或者, 为了描述这种或者的关系我们可以为某个谓词定义多个子句。
作者: NOD·AID
   
2006-8-27 10:11   回复此发言  

12回复:Prolog教程

?- connect(kitchen, office). 
yes 

?- connect(office, kitchen).
yes 

我们还可以让Prolog列出所有相连的房间。

?- connect(X,Y).
X = office 
Y = hall ;

X = kitchen 
Y = office ;

...
X = hall 
Y = office ; 

X = office
Y = kitchen ;
... 

使用我们现在所具有的知识,我们可以为“搜索Nani”加入更多的谓词。首先我们定义look/0,它能够显示玩家所在的房间,以及此房间中的物品和所有的出口。

先定义list_things/1,它能够列出某个房间中的物品。

list_things(Place) :- 
location(X, Place), 
tab(2), 
write(X),
nl,
fail. 

它和上一章中的最后一个例子差不多。我们可以如下使用它。

?- list_things(kitchen).
apple 
broccoli
crackers 
no 

这地方有一个小问题,它虽然把所有的东西都列出来了,但是最后那个no不太好看,并且如果我们把它和其他的规则连起来用时麻烦就更大了,因为此规则的最终结果都是fail。实际上它是我们扩充的I/O谓词,所以它应该总是成功的。我们可以很容易的解决这个问题。

list_things(Place) :- 
location(X, Place),
tab(2),
write(X),
nl, fail. 
list_things(AnyPlace). 

如上所示,加入list_things(AnyPlace)子句后就可以解决了,第一个list_things/1的子句把所有的物品列出,并且失败,而由于第二个子句永远是成功的,所以list_things/1也将是成功的。AnyPlace变量的值我们并不关心,所以我们可以使用无名变量‘_’来代替它。

list_things(_). 

下面我们来编写list_connections/1,它能够列出与某个房间相连的所有房间。

list_connections(Place) 
:- connect(Place, X),
tab(2),
write(X),
nl, 
fail.

list_connections(_).

我们来试试功能,

?- list_connections(hall). 
dining
room
office 
yes

终于可以来编写look/0了,

look :-
here(Place),
write('You are in the '), 
write(Place), 
nl,
write('You can see:'), 
nl,
list_things(Place), 
write('You can go to:'),
nl,
list_connections(Place). 

在我们定义的事实中有here(kitchen).它代表玩家所在的位置。以后我们将学习如何改变此事实。现在来试是功能吧,

?- look.
You are in the kitchen 
You can see:
apple 
broccoli
crackers 
You can go to:
office 
cellar 
dining 
room 
yes 

好了到此,我们已经学会了Prolog的基本编程方法,下一章将总结一下,并再举几个例子,此后我们将进入较深的学习。
作者: NOD·AID
   
2006-8-27 10:11   回复此发言  

13回复:Prolog教程
Prolog教程7-小结 
到现在为止,我们已经对Prolog有了一个基本的了解,现在有必要对我们所学过的知识做一个系统的总结。


Prolog的程序是由一系列的事实和规则组成的数据库。 
规则之间的调用是通过联合操作完成的,Prolog能够自动的完成模式匹配。 
规则还可以调用内部谓词,例如write/1。 
我们可以在Prolog的解释器中单独地对规则进行查询(调用)。 

在Prolog的程序的运行流程方面我有了如下的认识:


规则的运行是通过Prolog内建的回溯功能实现的。 
我们可以使用内部谓词fail来强制实现回溯。 
我们也可以通过加入一条参数为伪变量(下划线)无Body部分的子句,来实现强制让谓词成功。 

我们还学习了,


数据库中的事实代替了一般语言中的数据结构。 
回溯功能能够完成一般语言中的循环操作。 
而通过模式匹配能够完成一般语言中的判断操作。 
规则能够被单独地调试,它和一般语言中的模块相对应。 
而规则之间的调用和一般语言中的函数的调用类似。 

有了以上的知识,我们还可以编写出一些让其它语言的程序员吃惊的小程序。下面就举一个分析家谱的程序。

假如我们把家族成员之间的父子关系和夫妻关系,以及成员的性别属性定义为基本的事实数据库,我们就可以编出许多规则来判断其他的亲戚关系了。

例如我们有如下的数据库:

father(a,b). 
father(a,d).
father(a,t).
father(b,c).


wife(aw,a).
wife(bw,b).

male(t).
female(d).
male©.

father(a,b).表示a是b的父亲。
wife(aw,a). 表示aw是a的妻子。
male(t).表示b是男性。
female(d).表示d是女性。

上面我们并没有定义a、b、aw、bw的性别。 因为通过他们和其他人的关系我们可以很容易地确定他们的性别。不过要想让Prolog知道他们的性别我们就要定义如下的规则。

male(X):-father(X,_). 
female(X):-wife(X,_).

上面的male/1和female/1的谓词名称和事实的名称相同,这并不是什么特别的情况,你可以把所有定义相同的谓词的子句之间的关系想象“或者”的关系。也就是说:t和d是男性,或者如果X是其他人的父亲,则它也是男性。在判断性别时,我们并不关心此人是谁的父亲,所以后面一个变量用“_”代替了。

好了,假如有如下的询问:

?-male(t).
yes.

?-male(a).
yes.

?-male(X).
X=t;
X=c;
X=a;
X=a;
X=a;
X=b;
no.

最后一个询问,它虽然把所有的男性找了出来,可是它把a找了三次,原因很简单,因为我们有三个father/2的子句都包含a,好像不太理想,不过现在只能将就一下了,当我们学习了更多的知识后,就好解决了。

下面我们定义一些其他的亲戚关系的规则。你大概一看就能够理解。例如:X和Y是兄弟的条件是: X和Y有相同的父亲{father(Z,X),father(Z,Y)},并且他们都是男性{male(X),male(Y)},最后由于X和Y可以取相同的值,所以我们不得不加上一条X和Y不是同一个人{X/=Y}。

grandfather(X,Y):-father(X,Z),father(Z,Y).
mother(X,Y):-wife(X,Z),father(Z,Y).
brother(X,Y):-father(Z,X),father(Z,Y),male(X),male(Y),X/=Y.

当然我们还可以加入更复杂一点的规则,

uncle(X,Y):-brother(X,Z),father(Z,Y).

这个叔伯的规则uncle/2调用了前面的规则brother/2。

这里只是简单回顾一下前面所学习的知识,所以这个家族程序虽然可以使用,但是却极不完善。例如:它会把某一答案重复多次,还不能描述没有小孩的丈夫的性别。 我们这样改一下会更好一点:male(X):-wife(_,X)。因此,规则的定义是多种多样的,到底哪种更好、哪种更快,这就是我们以后所要研究的问题之一了。
作者: NOD·AID
   
2006-8-27 10:11   回复此发言  

14回复:Prolog教程
Prolog教程8-算术 
Prolog中也有一些能够进行数学计算的功能,但是数学计算是不好用逻辑的事物来描述的。因此计算一个数学表达式的方法和我们以前所学习的模式匹配有很大的区别。因此,Prolog专门提供了内部谓词is来计算数学表达式。其语法形式如下:

X is <数学表达式>

变量X将被赋值为表达式的值,在回溯时不赋值。数学表达式的形式和其他的语言相同。下面是使用Prolog计算的一些例子。

?- X is 2 + 2. 
X = 4 

?- X is 3 * 4 + 2. 
X = 14 

我们还可以使用括号,

?- X is 3 * (4 + 2). 
X = 18 

?- X is (8 / 4) / 2. 
X = 1

除了is以外,Prolog还提供了一些用来比较大小的操作符。

X > Y 
X < Y 
X >= Y 
X =< Y 

请注意>=和=<,它们的符号顺序是不能颠倒的。下面是一些例子,

?- 4 > 3. 
yes 

?- 4 < 3. 
no 

?- X is 2 + 2, X > 3. 
X = 4 

?- X is 2 + 2, 3 >= X. 
no 

?- 3+4 —> 3*2. 
yes 

我们可以在规则中使用这些符号,例如,

c_to_f(C,F) :- F is C * 9 / 5 + 32. 
freezing(F) :- F =< 32. 

c_to_f/2把摄氏温度转换为华氏温度,freezing判断华氏温度的冰点。下面是使用这些谓词的例子。

?- c_to_f(100,X). 
X = 212 
yes 

?- freezing(15). 
yes 


?- freezing(45).
no
作者: NOD·AID
   
2006-8-27 10:12   回复此发言  

15回复:Prolog教程
Prolog教程9-数据管理 
Prolog的程序就是谓词的数据库,我们通常把这些谓词的子句写入Prolog的程序中的。在运行Prolog时,解释器首先把所有的子句调入到内存中。所以这些写在程序中的子句都是固定不变的。那么有没有办法动态地控制内存中的子句呢?Prolog提供了这方面的功能。这就意味着,Prolog程序在运行过程中,还能够改变它自己。它使用一些内部谓词来完成这个功能。最重要的几个谓词如下:

asserta(X)
把子句X当作此子句的谓词的第一个子句加入到动态数据库中。它和I/O内部谓词的流程控制相同。回溯是失败,并且不会取消它所完成的工作。例如:如果内存中已经有了下面的几个事实:

people(a).
people(b).
people©.

如果运行了asserta(people(d))之后,内存中的people/1的子句就变成了下面这个样子:

people(d).
people(a).
people(b).
people©.

asserta(X)
和asserta/1的功能类似,只不过它把X子句追加为最后一个子句。 

retract(X)
把子句X从动态数据库中删除。此操作也是永久性的,也就是说回溯的时候不能撤销此操作。

* 在swi prolog中需要对动态操作的谓词名进行声明,例如前面如果希望能够动态修改people/1的子句,需要在程序最前面运行:
:-dynamic people/1.

能够动态的修改数据库显然是很重要的。它有助于我们完成“寻找Nani”。使用这些谓词,我们可以很方便地改变玩家和物体的位置。

下面我们来设计goto/1这个谓词,它能够把玩家从一个房间移到另一个房间。我们采取从顶向下的设计方法,这和我们设计look/0时的方法不同。

当玩家键入了goto命令之后,首先判断他能否去他想去的位置,如果可以,则移动到此位置,并把此位置的情况告诉玩家。

goto(Place):- can_go(Place), move(Place), look.

下面来一步一步地完成这些还没定义的谓词。

玩家所能够去的房间的条件是:此房间和玩家所在的房间是相通的,即:

can_go(Place):- here(X), connect(X, Place). 

我们可以马上测试一下,(假定玩家在厨房)

?- can_go(office). 
yes 

?- can_go(hall).
no

现在can_go/1已经可以工作了,但是如果它在失败时能够给出一条消息就很好了。所以还需要另外增加一条子句,如果第一条子句失败,也就是说不能去那个房间时,第二个子句将显示一条消息。

can_go(Place):- here(X), connect(X, Place). 
can_go(Place):- write('You can''t get there from here.'), nl, fail. 

注意第二条子句最后的那个fail,因为当目标与第二条子句匹配时,表示不能去此房间,所以它应该返回fail。这次的运行结果比上次要好多了。

?- can_go(hall). 
You can't get there from here. 
no 

下面再来设计move/1谓词,它必须能够动态的修改数据库中的here谓词的子句。首先把玩家的旧位置的数据删除,再加上新位置的数据。

move(Place):- retract(here(X)), asserta(here(Place)). 

现在我们可以使用goto/1在游戏的所有房间里走动了。

?- goto(office).
You are in the office 
You can see: 
desk 
computer 
You can go to:
hall 
kitchen 
yes 

?- goto(hall). 
You are in the hall 
You can see:
You can go to:
dining 
room
office 
yes 

?- goto(kitchen).
You can't get there from here.
no 

好像有点游戏的味道了。:)

下面开始编写take和put谓词,使用这两个谓词,我们可以拿取或丢弃游戏中的物品。使用have/1谓词来储存玩加身上所携带的物品,一开始,玩家身上没有物品,所以我们没有在程序的事实中定义have/1谓词。

take(X):- can_take(X), take_object(X). 

其中can_take(X)的设计方法与can_go/1相同。

can_take(Thing) :- here(Place), location(Thing, Place).
can_take(Thing) :-
write('There is no '), write(Thing), write(' here.'), nl, fail. 

take_object/1与move/1类似,它首先删除一条location/1的子句,然后添加一条have/1的子句。这反映出了物品从其所在位置移到玩家身上的过程。

take_object(X):- 
retract(location(X,_)), asserta(have(X)), write('taken'), nl.

正如我们所看到的那样,Prolog子句中的变量全部都是局部变量。与其他的语言不同,在Prolog中没有全局变量,取而代之的是Prolog的数据库。它使得所有的Prolog子句能够共享信息。而asserts和retracts就是控制这些全局数据的工具。

使用全局数据有助于在子句之间快速的传递信息。不过,这种方式隐藏了子句之间的调用关系,所以一旦程序出错,是很难找到原因的。

我们完全也可以不使用assert和retract来完成上述的功能,不过这就需要把信息作为参数在子句中传递。在这种情况下,游戏中的状态将使用谓词的参数来储存,而不是谓词的子句。每一个谓词的入口参数是当前状态,而出口参数则为此谓词修改后的状态,状态在谓词之间传递,从而达到了预期的目的。我们还将在以后的章节中介绍这种方法。

我们现在所编写的程序并不都是从纯逻辑的考虑出发的,不过你可以看出使用Prolog编写这个游戏的过程非常自然,并没有什么晦涩难懂的东西。

一般情况下,asserta等谓词是不会在回溯的时候还原数据库的,所以上面的几个数据管理谓词的内部流程与I/O谓词相同,不过我们可以很容易的编写出能够在回溯时取消修改的谓词。

backtracking_assert(X):- asserta(X). 
backtracking_assert(X):- retract(X),fail.

首先第一个子句被运行,在数据库中添加一条X子句。当其后的目标失败而产生回溯时,第二个子句将被调用,于是它把第一个子句的操作给取消了,又把子句X从数据库中上除了。
作者: NOD·AID
   
2006-8-27 10:12   回复此发言  

16回复:Prolog教程
Prolog教程10-递归 
递归的确是一种功能强大的编程算法,现在绝大部分的程序语言都支持函数的递归调用,Prolog也不例外,而且如果没有递归,Prolog就不能叫做Prolog了。

在Prolog中,当某个谓词的目标中包含了此谓词本身时,Prolog将进行递归调用。

正如前面所述的,某一规则被调用时,Prolog使用新的变量为此规则的body部分建立新的查询。由于每次的查询都是独立的,所以某一规则调用其自身与调用其他规则没有任何区别。

任何语言中的递归定义都包括两个部分:边界条件与递归部分。

边界条件定义最简单的情况。而递归部分,则首先解决一部分问题,然后再调用其自身来解决剩下的部分,每一次都将进行边界检测,如果剩下的部分已经是边界条件中所定义的情况时,那么递归就圆满成功了。

下面我们将定义一个能够检测某物体在其他物体中的谓词,这里将使用到递归。

以前所定义的location/2谓词,表述了手电筒(flashlight)在桌子(desk)里,而桌子在办公室(office)中。但是那时Prolog并不能判断手电筒是否在办公室中。

?- location(flashlight, office). 
no 

如果使用递归,我们就可以很轻松地写出谓词is_contained_in/2,它能够跟踪物体的所在的位置,因此它能判断手电筒是否在办公室中。

为了让问题更加有趣一些,我们再加入一些物品,它们的位置是一层一层地嵌套的。

location(envelope, desk). 
location(stamp, envelope).
location(key, envelope).

要想列出办公室中的所有物品,我们首先可以列出直接位于办公室的物品,例如桌子;然后,再列出桌子中的物品,再桌子中的物品中的物品......

如果把房间也看作一个物品的话,我们就可以很容易地写出具有两个部分的规则,它能够判断某物品是否在另一个物品中的。

如果物品T1直接位于物品T2中,则物品T1在物品T2中。(此为边界条件)

如果某一物品X直接位于T2中,而物品T1在物品X中(此处为递归调用),则物品T1在物品T2中。

用Prolog的语言来表达,上面的第一句可以写成,

is_contained_in(T1,T2) :- location(T1,T2). 

而第二句则是,

is_contained_in(T1,T2) :- location(X,T2), is_contained_in(T1,X). 

上面的递归很直接,请注意它是如何调用其自身的。

下面是此谓词的运行实例,

?- is_contained_in(X, office). 
X = desk ; 
X = computer ; 
X = flashlight ; 
X = envelope ; 
X = stamp ; 
X = key ; 
no 

?- is_contained_in(envelope, office).
yes 

?- is_contained_in(apple, office).
no 


递归的工作原理

规则中所定义的变量都是局部的。这意味着每次调用某一规则时,Prolog都将为此次调用新建一个独立的变量集。因此递归第一层的变量X、T1、T2,与第二层的变量X、T1、T2的变量名虽然相同,但是它们的值却是不同的。

我们可以使用带标号的变量或者Prolog的内部变量来区分这些局部变量。一开始,查询的目标是,

?- is_contained_in(XQ, office).

第一层递归的子句是:(在此使用带标号的变量来区分不同的递归级别,此处,T11表示是T1在第一层递归中的变量)

is_contained_in(T11, T21) :- location(X1, T21), is_contained_in(T11, X1). 

当查询的目标与此子句匹配时,变量的绑定情况如下:

XQ = _01 
T11 = _01 
T21 = office 
X1 = _02 

注意,查询目标中的变量XQ与T11同时绑定为_01,因此,一旦_01的值找到了,则XQ和T11的值也就同时找到了。

使用这些绑定后的变量,可以重写上面的子句,

is_contained_in(_01, office) :-
location(_02, office), is_contained_in(_01, _02). 

当locatio/2目标成功后,变量_02绑定为desk,即_02=desk,那么后面的递归调用就变成了,

is_contained_in(_01, desk) 
作者: NOD·AID
   
2006-8-27 10:13   回复此发言  

17回复:Prolog教程

这个新的目标将与is_contained_in/2的子句匹配,此时Prolog为本次匹配重新分配变量,,此时所有产生的变量如下,

XQ = _01 T11 = _01 T12 = _01 
T21 = office T22 = desk 
X1 = desk X2 = _03 

当最后的递归找了某个答案,例如envelope,则变量T12、T11、XQ将同时取为此值。下面是这个查询的详细步骤。

我们询问是, 
?- is_contained_in(X, office).
递归的每一层都有自己独立的变量,但是正如调用其他的规则一样,上一层的变量会与正在调用的那一层的变量之间通过绑定联系起来,在下面的程序跟踪中,将使用Prolog的内部变量来说明,这样可以很容易知道那些变量被绑定了。 

1-1 CALL is_contained_in(_0, office) 
1-1 try (1) is_contained_in(_0, office)
2-1 CALL location(_0, office) 
2-1 EXIT location(desk, office) 
1-1 EXIT is_contained_in(desk, office) 
X = desk ;
2-1 REDO location(_0, office) 
2-1 EXIT location(computer, office) 
1-1 EXIT is_contained_in(computer, office) 
X = computer ;
2-1 REDO location(_0,office) 
2-1 FAIL location(_0,office) 
当没有更多的location(X,office)子句时,is_contained_in/2的第一条子句就失败了,Prolog将试图满足第二条子句。请注意,在下面的调用中,location子句没有使用以前的变量,而是一个新的内部变量,_4。而T1仍然与_0绑定。

1-1 REDO is_contained_in(_0, office) 
1-1 try (2) is_contained_in(_0, office)
2-1 CALL location(_4, office) 
2-1 EXIT location(desk, office) 
当对is_contained_in/2进行一次新的调用时,与我们在解释器的提示符后面直接输入is_contained_in(X,desk)是完全相同的。这次调用将找出所有直接位于desk中的物品,正如上面找出直接位于office中的物品一样。

2-2 CALL is_contained_in(_0, desk) 
2-2 try (1) is_contained_in(_0, desk)
3-1 CALL location(_0, desk) 
3-1 EXIT location(flashlight, desk) 
在第二层的is_contained_in/2中找到了flashlight,这个答案将被传递到最上层的is_contained_in/2中。

2-2 EXIT is_contained_in(flashlight, desk) 
1-1 EXIT is_contained_in(flashlight, office) 
X = flashlight ;
同样,在第二层的递归中还找到了envelope。

3-1 REDO location(_0, desk) 
3-1 EXIT location(envelope, desk) 
2-2 EXIT is_contained_in(envelope, desk) 
1-1 EXIT is_contained_in(envelope, office) 
X = envelope ;
找完了桌子里面的东西后,它开始找桌子里的东西的里面的东西。

3-1 REDO location(_0, desk) 
3-1 FAIL location(_0, desk) 
2-2 REDO is_contained_in(_0, desk) 
2-2 try (2) is_contained_in(_0, desk)
3-1 CALL location(_7, desk) 
3-1 EXIT location(flashlight, desk) 
首先,看看flashlight里面还有没有东西,两个is_contained_in/2都失败了,因为在flashlight中找不到别的东西了。 

3-2 CALL is_contained_in(_0, flashlight) 
4-1 CALL location(_0, flashlight) 
4-1 FAIL location(_0, flashlight) 
3-2 REDO is_contained_in(_0, flashlight) 
3-2 try (2) is_contained_in(_0, flashlight)
4-1 CALL location(_11, flashlight) 
4-1 FAIL location(_11, flashlight) 
3-2 FAIL is_contained_in(_0, flashlight)
下面,再开始找envelope中的stamp。

3-1 REDO location(_7, desk) 
3-1 EXIT location(envelope, desk) 
3-2 CALL is_contained_in(_0, envelope) 
4-1 CALL location(_0, envelope) 
4-1 EXIT location(stamp, envelope) 
3-2 EXIT is_contained_in(stamp, envelope) 
2-2 EXIT is_contained_in(stamp, desk) 
1-1 EXIT is_contained_in(stamp, office) 
作者: NOD·AID
   
2006-8-27 10:13   回复此发言  

18回复:Prolog教程
X = stamp ;
然后是key。

4-1 REDO location(_0,envelope) 
4-1 EXIT location(key, envelope) 
3-2 EXIT is_contained_in(key, envelope) 
2-2 EXIT is_contained_in(key, desk) 
1-1 EXIT is_contained_in(key, office) 
X = key ;
再没有别的东西的,于是就一路失败回去。

3-2 REDO is_contained_in(_0, envelope) 
3-2 try (2) is_contained_in(_0, envelope)
4-1 CALL location(_11, envelope) 
4-1 EXIT location(stamp, envelope) 
4-2 CALL is_contained_in(_0, stamp) 
5-1 CALL location(_0, stamp) 
5-1 FAIL location(_0, stamp) 
4-2 REDO is_contained_in(_0, stamp) 
4-2 try(2) is_contained_in(_0, stamp)
5-1 CALL location(_14, stamp) 
5-1 FAIL location(_14, stamp) 
4-1 REDO location(_11, envelope) 
4-1 EXIT location(key, envelope) 
4-2 CALL is_contained_in(_0, key) 
4-2 try (1) is_contained_in(_0, key)
5-1 CALL location(_0, key) 
5-1 FAIL location(_0, key) 
4-2 REDO is_contained_in(_0, key) 
4-2 try (2) is_contained_in(_0, key)
5-1 CALL location(_14, key) 
5-1 FAIL location(_14, key) 
4-1 REDO location(_7, desk) 
4-1 FAIL location(_7, desk) 
3-1 REDO location(_4, office) 
3-1 EXIT location(computer, office) 
3-2 CALL is_contained_in(_0, computer) 
4-1 CALL location(_0, computer) 
4-1 FAIL location(_0, computer) 
3-2 REDO is_contained_in(_0, computer) 
4-1 CALL location(_7, computer) 
4-1 FAIL location(_7, computer) 
3-1 REDO location(_4, office) 
3-1 FAIL location(_4, office) 
no

 

优化

现在我们已经接触到了Prolog程序的一些神奇的地方,它所提供的编程方式不需要考虑程序的运行流程,而是注重于逻辑关系。但是,某些情况下,为了能够是程序快速的运行,我们不得不考虑这个问题。下面是一个例子。

首先目标location(X,Y)将于任何location/2子句匹配,而目标location(X,office)或location(envelope,X)只能与某些子句匹配。

下面我们来看一看is_contained_in/2谓词的第二条子句的两种写法。

is_contained_in(T1,T2):- location(X,T2), is_contained_in(T1,X). 

is_contained_in(T1,T2):- location(T1,X), is_contained_in(X,T2). 

它们都可以找到正确的答案,但是它们的运行性能将取决于我们的询问方式。当询问是is_contained_in(X,office)时,前者的运行速度较快。这是因为当T2绑定时,搜寻location(X,T2)目标将比两个变量都不绑定时容易。同样,后者却能够较快地完成查询is_contained_in(key,X)。

看样子,要想编出性能优越的程序还是要下一番工夫的啊。
作者: NOD·AID
   
2006-8-27 10:13   回复此发言  

19回复:Prolog教程
Prolog教程12-数据结构 
到目前为止,所介绍的事实、查询以及规则都使用的是最简单的数据结构。谓词的参数都是原子或者整数,这些都是Prolog的基本组成元素。例如我们所使用过的原子有:

office, apple flashlight, nani

通过把这些最简单的数据组合起来,可以生成复杂的数据类型,我们称之为结构。结构由结构名和一定数量的参数组成。这与以前所学过的目标和事实是一样的。

functor(arg1,arg2,...) 

结构的参数可以是简单的数据类型或者是另一个结构。现在在游戏中的物品都是由原子表示的,例如,desk、apple。但是使用结构可以更好的表达这些东西。下面的结构描述了物品的颜色、大小以及重量。

object(candle, red, small, 1).
object(apple, red, small, 1).
object(apple, green, small, 1).
object(table, blue, big, 50). 

这些结构可以直接取代原来的location/2中的参数。但是这里我们再定义一个谓词location_s/2。注意,虽然定义的结构较为复杂,但是它仍然是location_s/2的一个参数。

location_s(object(candle, red, small, 1), kitchen).
location_s(object(apple, red, small, 1), kitchen). 
location_s(object(apple, green, small, 1), kitchen).
location_s(object(table, blue, big, 50), kitchen). 

Prolog的变量是没有数据类型之分的,所以它可以很容易的绑定为结构,如同它绑定为原子一样。事实上,原子就是没有参数的最简单的结构。因此可以有如下的询问。

?- location_s(X, kitchen). 
X = object(candle, red, small, 1) ;
X = object(apple, red, small, 1) ;
X = object(apple, green, small, 1) ;
X = object(table, blue, big, 50) ;
no

我们还可以让变量绑定为结构中的某些参数,下面的询问可以找出厨房中所有红色的东西。

?- location_s(object(X, red, S, W), kitchen).
X = candle 
S = small
W = 1 ;

X = apple
S = small 
W = 1 ;

no

如果不关心大小和重量,可以使用下面的询问,其中变量‘_’是匿名变量。

?- location_s(object(X, red, _, _), kitchen). 
X = candle ;
X = apple ;
no

使用这些结构,可以使得游戏更加真实。例如,我们可以修改以前所编写的can_take/1谓词,使得只有较轻的物品才能被玩家携带。

can_take_s(Thing) :-
here(Room), 
location_s(object(Thing, _, small,_), Room). 

同时,也可以把不能拿取某物品的原因说得更详细一些,现在有两个拿不了物品的原因。为了让Prolog在回溯时不把两个原因同时显示出来,我们为每个原因建立一条子句。这里要用到内部谓词not/1,它的参数是一个目标,如果此目标失败,则它成功;目标成功则它失败。例如,

?- not( room(office) ). 
no

?- not( location(cabbage, 'living room') ) 
yes

注意,在Prolog中的not的意思是:不能通过当前数据库中的事实和规则推出查询的目标。下面是使用not重新编写的can_take_s/1。

can_take_s(Thing) :- 
here(Room),
location_s(object(Thing, _, small, _), Room).
can_take_s(Thing) :-
here(Room),
location_s(object(Thing, _, big, _), Room),
write('The '), write(Thing), 
write(' is too big to carry.'), nl,
fail.
can_take_s(Thing) :-
here(Room),
not (location_s(object(Thing, _, _, _), Room)),
write('There is no '), write(Thing), write(' here.'), nl,
fail.
下面来试试功能,假设玩家在厨房里。

?- can_take_s(candle).
yes 

?- can_take_s(table).
The table is too big to carry. 
no

?- can_take_s(desk). 
There is no desk here.
no 

原来的list_things/1谓词也可以加上一些功能,下面的list_things_s/1不但可以列出房间中的物品,还可以给出它们的描述。
作者: NOD·AID
   
2006-8-27 10:19   回复此发言  

20回复:Prolog教程

list_things_s(Place) :- 
location_s(object(Thing, Color, Size, Weight),Place),
write('A '),write(Size),tab(1),
write(Color),tab(1),
write(Thing), write(', weighing '),
write(Weight), write(' pounds'), nl,
fail.
list_things_s(_)

它的回答令人满意多了。

?- list_things_s(kitchen). 
A small red candle, weighing 1 pounds 
A small red apple, weighing 1 pounds
A small green apple, weighing 1 pounds 
A big blue table, weighing 50 pounds 
yes

如果你觉得使用1 pounds不太准确的话,我们可以再使用另一个谓词来解决此问题。

write_weight(1) :- write('1 pound'). 
write_weight(W) :- W > 1, write(W), write(' pounds').

下面试试看

?- write_weight(4). 
4 pounds 
yes 

?- write_weight(1). 
1 pound 
yes

第一个子句中不需要使用W=1这样的判断,我们可以直接把1写到谓词的参数中,因为只有为1时是使用单数,其他情况下都使用复数。第二个子句中需要加入W>1,要不然当重量为1时两条子句就同时满足。

结构可以任意的嵌套,下面使用dimension结构来描述物体的长、宽、高。

object(desk, brown, dimension(6,3,3), 90).

当然,也可以这样来表达物品的特性

object(desk, color(brown), size(large), weight(90)) 

下面是针对它的一条查询。

location_s(object(X, _, size(large), _), office). 

要注意变量的位置哟,不要搞混了。
 
 

 

 

 

 

Prolog教程12-联合 
Prolog的最强大的功能之一就是它内建了模式匹配的算法----联合(Unification)。以前我们所介绍的例子中的联合都是较为简单的。现在来仔细研究一下联合。下表中列出了联合操作的简要情况。

变量&任何项目: 变量可以与任何项目绑定,其中也包括变量 
原始项目&原始项目: 两个原始项目(原子或整数)只有当它们相同时才能联合。 
结构&结构: 如果两个结构的每个相应的参数能联合,那么这两个结构可以联合。 

为了更清楚地介绍联合操作,我们将使用Prolog的内部谓词‘=/2’,此谓词当它的两个参数能够联合时成功,反之则失败。它的语法如下:

=(arg1, arg2) 

为了方便阅读,也可以写成如下形式:

arg1 = arg2 

注意:此处的等号在Prolog中的意义与其他语言中的不同。它不是数学运算符或者赋值符。

使用=进行联合操作与Prolog使用目标与子句联合时相同。在回溯时,变量将被释放。

下面举了几个最简单的联合的例子。

?- a = a.
yes 

?- a = b.
no 

?- location(apple, kitchen) = location(apple, kitchen). 
yes 

?- location(apple, kitchen) = location(pear, kitchen). 
no

?- a(b,c(d,e(f,g))) = a(b,c(d,e(f,g))).
yes 

?- a(b,c(d,e(f,g))) = a(b,c(d,e(g,f))).
no 

在下面的例子中使用的变量,注意变量是如何绑定为某个值的。

?- X = a. 
X = a 

?- 4 = Y. 
Y = 4 

?- location(apple, kitchen) = location(apple, X).
X = kitchen 

当然也可以同时使用多个变量。

?- location(X,Y) = location(apple, kitchen).
X = apple
Y = kitchen 

?- location(apple, X) = location(Y, kitchen). 
X = kitchen
Y = apple

变量之间也可以联合。每个变量都对应一个Prolog的内部值。当两个变量之间进行联合时,Prolog就把它们标记为相同的值。在下面的例子中,我们假设Prolog使用‘_nn’,其中‘n’为数字,代表没有绑定的变量。

?- X = Y.
X = _01
Y = _01 

?- location(X, kitchen) = location(Y, kitchen).
X = _01
Y = _01

Prolog记住了被绑定在一起的变量,这将在后面的绑定中反映出来,请看下面的例子。 

?- X = Y, Y = hello.
X = hello 
Y = hello 

?- X = Y, a(Z) = a(Y), X = hello.
X = hello
Y = hello
Z = hello

最后的这个例子能够很好地说明Prolog的变量绑定与其他语言中的变量赋值的区别。请仔细分析下面的询问。

?- X = Y, Y = 3, write(X).
3
X = 3 
Y = 3

?- X = Y, tastes_yucky(X), write(Y).
broccoli
X = broccoli 
Y = broccoli

当两个含变量的结构之间进行联合时,变量所取的值使得这两个结构相同。

?- X = a(b,c).
X = a(b,c)

?- a(b,X) = a(b,c(d,e)).
X = c(d,e) 

?- a(b,X) = a(b,c(Y,e)).
X = c(_01,e) 
Y = _01

无论多么复杂,Prolog都将准确地记录下变量之间的关系,一旦某个变量绑定为某值,与之有关的变量都将改变。

?- a(b,X) = a(b,c(Y,e)), Y = hello.
X = c(hello, e) 
Y = hello 

?- food(X,Y) = Z, write(Z), nl, tastes_yucky(X), edible(Y), write(Z). food(_01,_02)
food(broccoli, apple) 
X = broccoli 
Y = apple
Z = food(broccoli, apple)

如果在两次绑定中变量的值发生冲突,那么目标就失败了。

?- a(b,X) = a(b,c(Y,e)), X = hello. 
no

上面的例子中,第二个子目标失败了,因为找不到一个y的值使得hello与c(Y,e)之间能够联合。而下面的例子是成功的。

?- a(b,X) = a(b,c(Y,e)), X = c(hello, e). 
X = c(hello, e)
Y = hello

如果变量不能绑定为某一可能的值,那么联合也将失败。

?- a(X) = a(b,c).
no 

?- a(b,c,d) = a(X,X,d).
no 

下面的这个例子很有趣,请你研究一下吧。

?- a(c,X,X) = a(Y,Y,b). 
no

你明白为什么这个例子失败么?第一个参数的绑定使得Y绑定为c,第二个参数之间的绑定告诉Prolog变量X与Y的值相同,那么X也绑定c,而最后一个参数的绑定使得X为b,有矛盾,所以失败了。这就是说没有什么办法能使得这两个结构联合。

匿名变量(_)不会绑定为任何值。所以也不要求它所出现的位置的值必须相同。

?- a(c,X,X) = a(_,_,b). 
X = b

如果使用(=)那么联合操作时显式的。而Prolog在使用子句与目标匹配时的联合则是隐式的。
作者: NOD·AID
   
2006-8-27 10:19   回复此发言  

21回复:Prolog教程
Prolog教程11-数据结构 
到目前为止,所介绍的事实、查询以及规则都使用的是最简单的数据结构。谓词的参数都是原子或者整数,这些都是Prolog的基本组成元素。例如我们所使用过的原子有:

office, apple flashlight, nani

通过把这些最简单的数据组合起来,可以生成复杂的数据类型,我们称之为结构。结构由结构名和一定数量的参数组成。这与以前所学过的目标和事实是一样的。

functor(arg1,arg2,...) 

结构的参数可以是简单的数据类型或者是另一个结构。现在在游戏中的物品都是由原子表示的,例如,desk、apple。但是使用结构可以更好的表达这些东西。下面的结构描述了物品的颜色、大小以及重量。

object(candle, red, small, 1).
object(apple, red, small, 1).
object(apple, green, small, 1).
object(table, blue, big, 50). 

这些结构可以直接取代原来的location/2中的参数。但是这里我们再定义一个谓词location_s/2。注意,虽然定义的结构较为复杂,但是它仍然是location_s/2的一个参数。

location_s(object(candle, red, small, 1), kitchen).
location_s(object(apple, red, small, 1), kitchen). 
location_s(object(apple, green, small, 1), kitchen).
location_s(object(table, blue, big, 50), kitchen). 

Prolog的变量是没有数据类型之分的,所以它可以很容易的绑定为结构,如同它绑定为原子一样。事实上,原子就是没有参数的最简单的结构。因此可以有如下的询问。

?- location_s(X, kitchen). 
X = object(candle, red, small, 1) ;
X = object(apple, red, small, 1) ;
X = object(apple, green, small, 1) ;
X = object(table, blue, big, 50) ;
no

我们还可以让变量绑定为结构中的某些参数,下面的询问可以找出厨房中所有红色的东西。

?- location_s(object(X, red, S, W), kitchen).
X = candle 
S = small
W = 1 ;

X = apple
S = small 
W = 1 ;

no

如果不关心大小和重量,可以使用下面的询问,其中变量‘_’是匿名变量。

?- location_s(object(X, red, _, _), kitchen). 
X = candle ;
X = apple ;
no

使用这些结构,可以使得游戏更加真实。例如,我们可以修改以前所编写的can_take/1谓词,使得只有较轻的物品才能被玩家携带。

can_take_s(Thing) :-
here(Room), 
location_s(object(Thing, _, small,_), Room). 

同时,也可以把不能拿取某物品的原因说得更详细一些,现在有两个拿不了物品的原因。为了让Prolog在回溯时不把两个原因同时显示出来,我们为每个原因建立一条子句。这里要用到内部谓词not/1,它的参数是一个目标,如果此目标失败,则它成功;目标成功则它失败。例如,

?- not( room(office) ). 
no

?- not( location(cabbage, 'living room') ) 
yes

注意,在Pro
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值