知识工程T-PROLOG

更新时间:2024-06-28 03:36:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

PROLOG语言

PROLOG是英文“PROgramming in LOG”的缩写,意思为逻辑程序设计,它最早由R·Knowalski首先提出。世界上第一个PROLOG系统于1972年由A·Colmerauer及其研究小组在法国马赛研制成功。

PROLOG以逻辑程序设计为基础,最初设计PROLOG的目的是作为处理逻辑推理问题的会话式语言,并以处理一阶谓词演算为背景。后来,由于PROLOG具有简洁的文法,丰富的表达力和独特的非过程型等特点,从而日益受到了计算机界的重视,引起了越来越多的人的注意,现在PROLOG语言已被广泛应用于关系数据库、抽象问题求解、数理逻辑、公式处理、自然语言理解、专家系统以及人工智能的许多领域。

PROLOG是一种人工智能语言,它同其它大多数程序设计语言一样,也有许多不同的实现系统,它们具有各自的语义语法特色。本章主要介绍一种称为“核心PROLOG”的标准文本,目前,多数PROLOG系统基本上遵从这个标准文本的规定。

一、PROLOG的基本内容

1.PROLOG的特点

作为一种程序设计语言,PROLOG具有两方面的特性:一是它描述求解问题的方式;二是语言本身的特点。

如众所周知,用通常程序设计语言(如FORTRAN,PASCAL)来解问题时需指明算法,即对一给定的问题指明一系列计算机要执行的计算步骤,告诉计算机“如何做”。与这些通常的程序设计语言不同,PROLOG语言求解问题时,要求程序员描述所解问题中的对象和反映它们之间关系的某些已知事实,描述和定义诸对象和它们之间关系的某些规则。它强调描述对象(和事实)之间的逻辑关系,程序员一般不必告诉计算机运行执行的先后次序。因此,从能够描述问题本

身,而不必描述求解问题的详细计算步骤这一点讲,PROLOG是更高级的语言,它可看作为一种描述性语言。

目前,人们往往把语言分为函数型语言、逻辑型语言和其它语言,这是因为这三类语言在性质上和描述问题的方式上有着很大的差异。

PROLOG语言除了上述的特性外,还有下面一些特点:

①PROLOG的数据和程序结构统一,PROLOG提供一种一致的数据结构,称为项(term)。所有数据和程序都由项构造而成的。在智能程序中常需要将一段程序的输出数据作为新产生的程序来执行,因此人工智能语言就具有数据和程序结构一致的特性,LISP在这方面是当之无愧的,它的程序和数据都是S—表达式。而PROLOG也不逊色,它的程序和数据都是以项为基本单位,并且都是树型结构。

②PROLOG能够自动实现模式匹配和回溯,这些是人工智能系统中最常使用的,最基本的操作。由于PROLOG系统提供了自动完成这些操作的功能,使用户在PROLOG语言这一级上不必考虑这些问题。

③与LISP语言一样,递归是PROLOG语言的重要特点,它反映在程序和数据结构中,由于这一特性,一个大的数据结构常常能够由一个小的程序来处理。

PROLOG的所有这些特性,使得PROLOG语言特别适用于描述智能程序,因此人们习惯把PROLOG称为人工智能程序设计语言,并将它作为进行人工智能系统设计的工具。

2、PROLOG的简单实例

我们从一个简单例子开始介绍PROLOG程序,设有一有向图,如图6-8所示,现用PROLOG来描述图中的两点之间的关系。

c d e a b

图6-8 一个简单的有向图例子

若用connected(X,Y)表示从X到Y有一条有向边,则图6-8可用下面的PROLOG事实来描述:

connected(e,b). connected(c,d). connected(d,e).

现在定义两点有通路的概念,点X到Y有通路是指:

①X到Y有一条有向边。

②存在一点Z,X到Z有一条有向边,并且Z到Y有一通路。 上面这个通路的定义,可以用PROLOG的规则来描述: path(X,Y):—connected(X,Y).

path(X,Y):—connected(X,Z),path(Z,Y).

这path(X, Y)表示X到Y有一通路,符号“:—”表示“如果”,逗号“,”表示“并且”。

因此上面第一条规则的含义是:如果X到Y有一条有向边,则X到Y有通路。第二条规则的含义是:如果X到Z有一条有向边,并且Z到Y有通路,则X到Y有通路。

输入了上述事实的规则之后,就可以向机器询问图中各点之间的关系。 ?—path(a, b). a到b有通路吗?

yes

?—path(b, a). b到a有通路吗?

no

?—path(d, Y). d到哪一点有通路?

Y=e (Y代表变量)

?—path(b, X), path(c, X).

x=d

以上就是一个简单的PROLOG程序,以及一旦这个程序进入机器后,用户以会话方式运行这个程序的情况:

从这个实例中,我们可以看出,PROLOG语言提供了三种基本语句: ①事实:它说明一个问题中的对象和它们之间的关系的一些已知事实。 ②规则:它用来定义对象和它们之间的关系,用来描述一个事实依赖于其它

一组事实。

③用来询问有关对象和它们之间的关系。 3.事实

从上一节看到,为了表达从点a到b有一条有向边这一事实,写了connected(a, b)。这个事实由两个对象(a和b)以及一个关系(connected)所组成。

在PROLOG中事实一般形式是:

①首先写关系,接着是括在一对圆括号内的若干对象,对象间用逗号隔开。 ②对象和关系名必须以小写字母开头。 ③在事实的末尾名必须写上实心点“·”。

在PROLOG中,关系connected被称为谓词,括号中的(a, b)被称为变元,谓词名是任意的,变元的个数也是任意的,但应注意在圆括号中的诸变元的书写次序。实际上,这种次序是任意的,但在书写事实时必须确定某种次序,一旦确定后,在以后这一事实的使用时要保持这种次序的一致性,例如事实connected(a, b)是指从点a到b有一条有向边,下面是一些事实例子以及解释:

on(book, desk). 书在桌子上面 owns(john, book). john有书 valuable(gold). 金是值钱的 father(john,mary). joho是mary的父亲 play(jane, jin, badminton) . jane和jin打羽毛球 4.规则

假设我们要叙述这样的事实:john喜欢所有的人,表示这一事实的一种方法是写出下列所有各个事实:

likes (john, david). likes (john, tom). likes (john, jane). ?

显然这种表示法是不方便的。因此表示john喜欢所有人的另一种方法是把它说成:“john喜欢任一对象,只要这一对象是人。”于是,便可以用下面的规则来表示:

likes(john, X):—person(X).

它的含义是:如果X是人,则john就喜欢这个X所指的人。注意,这里X是大写字母代表变量。

在PROLOG中,当你要描述一个事实依赖于其它一组事实时,则用规则来表示。我们用“如果”、“则”来表示一个规则,例如:

“如果某人有钱,则他就买汽车。” 用规则描述为:

buy(X, car):-have(X, money). 规则可用来表示定义,例如:

如果X是一动物,并且X有羽毛,则X是鸟。 bird(X):-animal(X), feather(X).

从上面例子可以看出:规则是诸对象和它们的关系的一般陈述。一个规则由头和体组成。头是:—符号的左部,体是“:—”符号的右部。“ :—”的意思是如果,规则以“.”结尾。

规则的头描述了这条规则企图定义的事实。规则的体描述了一些目标的连结。为使头成为真的,这些目标必须逐个被满足。体中目标一个接着一个写,中间用逗号“,”分开,逗号的意思是并且,它们把体中目标连结起来。

现把规则的各部分形象地描述如下: bird(X) :- animal(X),feathers(X). 头 如果 并且 体

这里的“:—”也读作“只要”。显然规则的这种形式类似于Horn子句。 通常一个谓词能用事实和规则的混合来定义。 例:likes(john, food).

likes(john, X):- person(X).

我们再举出规则的几个例子:John喜欢任何喜欢讲话的人。换句话说,John喜欢某个人,如果这个喜欢讲话。

用规则表示:

like(john, X):-person(X), likes(X, talk).

如果我们希望表示,John喜欢任何喜欢讲话和游泳的人,则只要简单地加一目标到上面体中,并用逗号隔开。

likes(john,X) :- person(X), likes(X, talk),likes(X, swim). 若要表示父母和(外)祖父母等关系,可用下列规则: parent(X, Y):—mother(X, Y). parent(X, Y):—father(X, Y).

grandparent(X, Y):—parent(X, Z), parent(Z, Y). 5.询问

我们可以对一些事实询问,在PROLOG中一个问题很象一个事实,只要在它前面加一特殊符号“?—”。例如:

?—owns(mary, book)

其意思是Mary有这本书吗?或Mary有这本书是真的吗?但不能问她是否有所有的书。

当询问时,PROLOG要搜索事先输入的事实组成的数据库,看是否有事实与问题相匹配。当事实与询问的关系相同且相应的自变时也相同时,我们说二者相匹配。如果PROLOG找到一个事实与问题相匹配,则回答“是”;否则,数据库里无此事实则回答“否”,上面询问中的owns(mary,book), 又称为目标,或者说询问自由目标组成。

假设已有如下事实。 on(book, desk). owns(john, book). female(jane).

play(jane,jin, badminton). valuable(gold) 则可以问:

?—female(jane).

yes

?—owns(john, moon).

no

这里下划线的部分表示PROLOG回答的信息(以下同)。

?—male(tom).

no

对前面两个问题的回答是清楚的。对第三个问题,PROLOG也回答no,这是因为数据库中没有这样的事实,这个no意味着“就我所知,这是不对的。”因此系统回答no有两种意思:不或者不知道。

显然,仅仅询问这样的问题。获得yes和no的回答是不够的。询问象“John有什么东西?”,“谁与谁打羽毛球?”也是用户常提出的问题,但这些问题必须借助变量来提出。如下所示:

?—owns(john, X)

X=book

?—play(X, Y, badminton)

X=jane, Y=jin ?—owns(X, book).

X=john

在介绍规则时,我们曾引入“连结”的概念,即用逗号“,”把一规则体中的多个目标连结在一起,这里“,”表示“并且”。

同样,在询问中也可以将几个目标用“,”连续起来放在“?—”的后面,以便我们问更复杂关系的问题,如:

?—play(X, jin, badminton),female(X). X=jane.

至此,我们已经介绍了PROLOG基本的核心内容,主要是: ①说明有关对象的事实。 ②用规则的形式表示关系。 ③问有关事实和规则的问题。 ④用大写字母开关的字符串表示变量。 ⑤用连结符号“,”表示并且。

二、数据结构和递归

1.项

PROLOG提供一个一致的数据结构称为项,所有的数据和PROLOG程序都是由项构造而成的。PROLOG中项的定义用BNF形式可写成;

<项>∷=<常量>|<变量>|<结构>|<(项)>|

项可以是常量、变量、结构或者括在括号里的项。在前面,所有这些项我们都已经碰到,只是不熟悉它们的名称。

(1)常量

原子或者是整数,即<常量>∷=<原子>|<整数>。

原子用来标识对象的名字、谓词(对象间关系)等。原子是以小写字母开头的字母数字串,中间可插有下划线符号“—”或仅由符号组成。整数用来表示数,使得PROLOG能执行算术运算。整数由数字组成,不包含小数点。

(2)变量

用来表示暂时不能命名或不需要命名的对象,变量是以大写字母或下划线符号“—”开头的字母数字串,中间可插有“—”。无名变量表示为“—”,因它的名字从来不会被使用。、

在同一个子句中的几个无名变量,不需要给出一致的解释。这是无名变量的特有性质,它与在同一个子句中,一个有名变量的多次出现要有一致的解释不同。

(3)结构

或称复合项。它是由一组其它对象(也可为结构)组成的单个对象,这些其它对象称为它的成分,把几个成分组合成一个结构作为单个对象是很有用处的,结构的定义的;

<结构>∷=<函数符>(<项>{,<项>}) <函数符>∷=<原子> 下面是一些结构的例子: owns(john, book)

owns(john,book(prolog, author(clocksin, Melish)) a (b, c, d) 3+5*2 gcd(X, f(X))

其中第四个例子是结构+(3,*(5,2))的另一种表示。为对应这种形式,有人在结构定义中加入<项><原子><项>{<原子><项>},这样第四个例子中3是项,+ 是原子,5是项,*是原子,2是项,其它四个例子均符合定义中的<函数符>(<项>{,<项>})。

若把一个复杂的结构表示成一棵树将更容易理解。例如,把上面例子中每一

个函数符(如owns,author等)看作为根(或子根),它的变元为分枝,而每一个分枝又可指向另一个结构,这样就有图6-9的树形结构。

2.表和它的递归性

表是非数值程序设计中一种最常用的数据结构。LISP与PROLOG都以此作为它们的数据结构。所不同的是,在PROLOG中表仅为一特殊类的结构;而在LISP中表是唯一的数据结构。表是元素的有序序列,有序指序列中元素的次序是重要的。PROLOG中,一个表的元素可以是原子,结构或任何其它项包括其它表,因此表是递归定义的。事实上,表能够表示人们在符号处理中希望使用的任一类结构。

PROLOG中的表或者是一个没有元素的空表,或者是一个结构,它包括头和尾两个成分,空表写成[],非空表写·(头,尾),这里“·”是一个函数符。表的头和尾是它的两个成分(变元)。当尾没有元素时,尾写为空表。例如由元素a组成的表为:·(a,[])。

同样,由原子a,b,c组成的表: ·(a,·(b,·(c,[])))

由此可见,表能表示成二元树。

用点表示法写复杂的表极不方便,而且难看,因此PROLOG也用称为表表示法的方法未写表。表表示法是用逗号把表的诸元素分开,并且把所有元素括在方括号内,例如上面两个表用表表示法可写成为[a]和[a,b,c]。

根据表表示法和点表示的意义,[a,b,c]可写成·(a,[b,c]),进一步写成·(a,·(b,[c]))以及·(a,·(b, ·(c,[])))。在表中包含其它表或变量是十分有用的。例如下面的表在PROLOG中最合法的:

[]空表 [a,b,c,[d,e,f],g] [a,V1,b,[X,Y]]

表表示法是PROLOG实际使用的表示形式、表的大小是最外层元素的个数。因此上面三个表的大小分别为0,5,4。

对表的操作是通过把表分成头和尾来进行的,在表表示法中,头是表中最外层的第一个元素,表的尾是一个表,这个表由原来表的除了第一个元素外的所有元素组成。

为了对表进行取表头和取表尾的操作,在PROLOG中有一种专门表示法来

表示一个分明头X和尾Y的表,它写成[X |Y],这里分隔X和Y的符号是垂杠“|”。当这个模式匹配任一表时,PROLOG将把X实例化为匹配的表的头,Y实例化为该表的尾,例如,表[a,b,c.d]的[X|Y]形式为[a|[b,c,d]],所以X=a, Y=[b,c,d]因为[a,b,c,d]的表头为a,表尾为[b,c,d],同样,表[a]的[X|Y]形式为[a|[ ] ],所以X=a,Y=[ ],再看下面一些例子。

若有p([1,2,3]).

p([the,cat,sat,[on,the,mat]]). 于是?—p([X|Y])

X=1,Y=[2, 3]; / *打;号*/ – – – – – – – – – – – – – – X=the, Y=[cat,sat,[on,the,mat]]

– – – – – – – – – – – – – – – – – – – – – – – – – ?—p([_, _, _,[_|X]].

X=[the,mat] – – – – – – – – – – – – ?—p([X, Y|Z]).

X=1, Y=2,Z=[3]; – – – – – – – – – – – – – – X=the, Y=cat, Z=[sat,[on,the,mat]] – – – – – – – – – – – – – – – – – – – – 3. 美术运算

LISP和PROLOG均不得以数值计算为其目标的,它们主要用于非数值程序设计。因此,在算术运算方面它们不能与传统的FORTRAN,PASCAL相比拟,它们只提供了一些基本的运算,PROLOG提供的五种最基本的算术运算是:加、减、乘、除和取模,写成一般的中级形式为XopY,其中OP是算术运算符,可为+ ,-,*,/,mod,/是指整数除,即X、Y为整数,X/Y是X除以Y的商(整数)。Xmod Y是X除以Y的余数。

算术运算符的优先级与通常数学上的规定相同,即*,/,mod高于+,-。 所以:

X+Y * Z X+(Y * Z) 两者一样

A-B/C A-(B/C) 两者一样 X+Y/B-C (X+(Y/B))-C 两者一样 在PROLOG中,上面的算术表达式也可写为如下结构形式: +(X,* (Y,Z)) -(A,/(B,C)) -(+(X,/(Y,B)),C)

上述中算术运算符是作为函数符出现的。一般形式为:算术运算符(变元,变元)。+(X,*(Y,Z))中乘法比加法先做,因为*结构是+结构中的一个变元。对于+结构,为了+结构,为了知道它的两个变元,必须先做乘法。到底使用哪种形式表示算术表达式,由用户决定。

对于相同优先级的运算符,PROLOG采用通常的左结合规则,即A+B-C意味着(A+B)-C,8/2/2意味着(8/2)/2,算术表达式中可按需要使用圆括号。

在PROLOG中,需要使用一特殊运算符“is”来告诉PROLFOG求值算术表达式。运算符is是中级运算符,通常它取一个变量在左边,一个算术表达式在右边。is意味着对后面的算术表达式求值,因此算术表达式中的所有变量的值在求值时必须都是已知的。使用is对算术表达式求值的一般形式为:变量is算术表达式。

例如,X is A+B表示对A+B求值,若X原先是未实例化的,则现在被实例化为A+B的值。之所以用is,因为A+B也是一个普通的PROLOG结构,而PROLOG对结构一般是不求值的。例如对结构likes(john,wine)不可能求值,因为like不是一个算术运算符,因此为了对算术表达式求值,必须用求值运算is来指明。记住,A+B这样的成分只是一个通常的PROLOG结构,好象likes(john,wine)结构一样,后面将说明这一点。

下面给出两个简单的例子说明算术表达式的使用。 例1 计算国家的人口密度

1997年一些国家的人口数和面积用PROLOG表示有: pop (usa, 212). pop (china, 825). pop (india, 586). area (usa, 3609).

area (china, 3707). area(india, 1139).

因一些PROLOG系统中只能使用不太大的整数,所以用百万表示人口数的单位,即pop(C,P)表示国家C有P百万人口,用千平方英里为面积单位,即area(C,A)表示国家C有A千平方英里面积,于是,表示人口密度的规则是(人/平方英里):

density(X,Y):- pop(X,P),area(X,A),Y is P*1000/A. 规则的意思为:

“如果国家X的人口数为P百万人,且国家X的面积为A千平方英里,并且Y是P乘以1000除以A,则国家X的人口密度是Y人/平方英里。”

上例中当PROLOG做到is时,若Y是未知的,则在求值P*1000/A后让Y代表这个值(即Y被实例化这个值)。这意味在is右边的变量必须是已知的。例如:

当打入?-density (china, X).

X=222

例2 add(X, Y, Z):- Z is X+Y

这是一个加法规则,意味着Z是X加Y。 因此当你问:

?—add (2,3,4).

no ?—add(2,3,5)

yes

第一个询问因为4不是2+3,所以回答no,第二个问题5=2+3,所以回答yes,若问:

?—add(2,3,X)

X=5

从这个实例可以看出,is的作用解释为当它的左边是一个未实例化的变量时,则is的作用是把该变量实例化为is右边代表的值。若它的左边是一个已知数(包括实例化的变量),则is的作用好象等号“=”,作相等检查。统一这两种

情况,is的含义是“是”。注意,若询问?—add(X,3.5),不可期望得到X=2,因为is右边的X值未知,PROLOG将用完自由空间而告出错(Nospace left)。

习惯于传统程序设计语言的程序员必须清楚地知道,is决不是赋值。假设X已实例化为8,如果写X is X-1,由X决不会取7,X is X-1作为目标永远失败,因为8不是7,所以8 is 7失败,同样,假设Y未实例化,若连续写Y is 5,Y is 4,则Y为实例化为5,不可能是4,第二个目标Y is 4失败。

4.比较运算

在程序中经常会出现数的比较,PROLOG提供了六种常用的比较运算,并以中缀形式表示:

X=Y (X和Y代表相同数) X \\ =Y (X和Y代表不同数) X<Y (X小于Y) X>Y (X大于Y) X=<Y (X小于等于Y) X>=Y (X大于等于Y)

这里X,Y可以是整数,或者是被实例化为整数的变量。XopY可以作为目标出现在询问和(或)规则中,此时,如果A大于B,则目标A>B成功。例如若询问?—2*3>2*4,则回答no;若问?—2*3<2*4,则回答yes。XopY也可作为变元出现在结构中。

例3,判断给定的3条边是否构成等边三角形。 equilateral(X,Y,Z):- X>0, X=Y,Y=Z. 于是?—equilateral(8,8,8).

yes – –

?—equilateral(8,7,9) no – –

如果问?—equilateral(8,8,6+2). 将回答什么?回答no。

六个比较运算符在PROLOG中是作为内部谓词的,其中“=”和“\\=”不仅能用于整数间比较,也能用于其它对象的比较。

对目标X=Y(其中X和Y是任意两个项),决定X和Y是否相等的规则是: ①原子和数总是等于自身,例 atom=atom,9=9 成功 atom=atoml,1984=1985 失败。

②若X是未实例化的变量,Y是任一对象,则X=Y,总是相等,其副作用是X被实例化为Y代表的对象。例:

?—rides (john,bike)=X 成功,且X被实例化为项rides(john,bike)。 ③两个结构相等,并且仅当它们有相同的函数符和变元个数,且对应的变元都相等,例如,下面的目标成功并且X被实例化为bike。

rides(john,bike)=rides(john,X)

结构能够嵌套,如果测试嵌套结构的相等性,则需花时间逐层的测试各结构,例:

a (b,c,d(e,F,g(h,i,j)))=a(B,C,D(E,f,g(H,i,J)))成功,B,C,E,F,H和J分别被实现为b,c,e,f,h和j。

如果对两个未实例化变量进行相等性测试,将出现第②点的特殊情况,其结果是目标成功,并且两个变量成为共享。

谓词“\\=”称为不等。如果X\\=Y失败,则X=Y成功,若X=Y成功,则X\\=Y失败。

5.程序和递归性及其例子

下面从讨论PROLOG中的几个内部谓词开始,介绍程序的递归性。 (1)member谓词,它用来确定一给定的元素是否为一给定表的成员。 例如,设有表[a,b,c,d,f,h],现要知道d或e是否在表中,同LISP一样,PROLOG也是先判别这个元素是否为该表的头。若是,则该元素在表中;否则检查这个元素是否在这表的尾中(可用member本身完成)。如果这个元素是该表的尾的成员,则它是该表的元素,即:

member(X,表):—member(X,表的尾)。 因此程序如下: member(X,[X | _]).

member(X,[ _ | _]):—member(X,Y).

第一个事实是说,如果X是表的头,则X是表的成员。这里利用了无名变量“_”,因为此时我们并不关心它的尾是什么,这个事实也可写为规则:

membr(X,[Y | _]):—X=Y.

第二个规则是说,如果X是该表的尾Y的成员,则X是该表的成员,这里也用了无名变量,此时表示我们对表头不感兴趣。

上面在定义member谓词时又调用了它自己,因此member谓词是递归定义的。现在再回到一开始提的问题,d或e是否在表[a,b,c,d,f,h]中?可询问:

?—member(d,[a,b,c,d,f,h])

Yes

?—member(d,[e,b,c,d,f,h])

no

(2)append谓词。同LISP的append函数一样,用来把两个表连结成一个表。例如[a]和[b]连结成[a,b]。分两种情况:

①终结情况,当第一个表为空表时,任何表加到空表仍为那个表。 ②否则,把第一个表的尾和第二个表连结,再把第一个表的头加入连结结果的表的前面作为头。

于是append谓词的定义为: /*1*/ append ([],L,L).

/*2*/ append([x| L1],L2,[x| L3]):—append(L1,L2,L3)

当不断地递归应用上面第二个规则,第一变元将逐步减少到空表,所以终结条件最终会发生。

使用append的其它例子,如:

?—append([a,b,c],[1,2,3],[a,b,c,1,2,3]).

yes

?—append ([a,b],Result,[a,b,a,b,x,y]).

Result=[a,b,x,y]

?—append (,[a,girl],[she,is,a,girl]).

X=[she is]

这些例子说明PROLOG中变量实例化的含义。在目标append(X、Y、Z)中,三个变元的任何一个都可以为变量,甚至当Z为表时,X和Y均可为变量。这种性质有人称为可逆性。

(3)用PROLOG写last函数,即求出给定表的最后一个元素。 分析:如果表L只有一个元素,则该元素即为最后一个元素。

如果表L有多于一个元素的元素,则去掉头元素,求出剩余表(即尾)的最后一个元素。目标last(X,L),表示表L的最后一个元素为X,其程序如下:

last(X,[X]).

last(X,[_|Y]):—last(X,Y). (4)谓词length,求表的长度。 length([],0):—!.

length([H|T],X):—length(T,Y),X is Y+1.

这里的“!”符号将在以后介绍,它的作用是使得调用谓词length的目标不可重新满足。例如,当用户通过询问length([1,2,3,4],L)得到L=4后,若他要求再重新满足length(1,2,3,4],L)这个目标(通过打;和return键),则失败。目前读者在读这个程序时可以认为“!”符号不存在(下同)。

(5)写一个PROLOG程序求Fibonacci数,Fibonacci序列定义为: fo=1 f1=1

fn=fn-1 + f n-2 n≥2

设fib(N,X)表示第N个Fibonacci 数为X,则程序为: fib(0,1):-! fib(1,1):-!

fib(N,X):-N1 is N-1,

N2 is N-2

fib(N1,Z1),fib(N2,Z2), X is Z1+Z2.

6.递归注意点

在使用递归定义时,须注意以下两点: (1)不能写循环递归定义。 例如:parent(X,Y):- child(Y,X).

child(A,B):- parent(B,A).

在这例子中,为满足parent(X,Y),建立了child(Y,X)作为子目标,而谓词child的定义中利用了parent(B,A)作为子目标。这样在问有关child和parent的问题时,将导致无限循环,PROLOG永远不能推出任何结果。

(2)左递归定义的问题。 例如:

person(X):- person(Y),mother(X,Y). person(adam).

这里第一个规则是左递归定义,即定义person(X)时,它的第一个(子句体中左面的一个)子目标就是person本身。于是当一目标匹配这一规则时,将首先要满足这个子目标,而这个子目标本质上等价于原始目标,因此该规则又将被利用。例如:?—person(X)

PROLOG将首先利用规则,且产生子目标person(Y)。为了满足这一子目标,PROLOG再一次取第一个规则,并且产生另一个等价的子目标。这样如此继续,直到用完空间。当然,如果有机会去找到事实person(adam),则将开始产生解。问题是PROLOG的搜索规则是必须在试验第一种可能性失败后才能搜索第二个可能。而在这里,第一个规则的寻找任务是无限长,为了避免上面这种左递归引起的无限循环,可以把事实放在规则之前,即

person(adam). person(X):— person(Y),mother(X,Y) 事实上,作为一般启示,只要有可能总应把事实放在规则之前。

三、PROLOG的搜索方法

1.关于PROLOG的控制

众所周知,计算机越来越广泛地用于解决非数值问题,对于这一类应用,最自然的数据结构,基本操作和控制结构与那些主要用来进行数值计算的数据结构、基本操作的控制结构有着很大的不同,作为非数值问题处理语言,PROLOG的数据结构适应于这类问题的要求。

PROLOG系统实现自动搜索、自动进行模式匹配以及回溯,这便把人工智能系统中最基本的操作和实现技术加入PROLOG实现系统内部,从而大大简化了所解问题的表述。

通过前面的叙述,大家已经看到,PROLOG程序不同于一般程序语言所写的程序。在PROLOG程序中不存在一般语言所具有的条件、循环和转向等控制结构成份,它仅含有事实、规则和询问。PROLOG系统根据用户给出的所解问题的已知事实和有关规则这些前提,自动地进行搜索查找、判断和推理,最后回答用户提出的问题,因此,PROLOG把实现搜索匹配和回溯的控制操作放进了系统内部,从而使用户一级上的控制达到了更高一级水平。

PROLOG系统还提供了不少内部谓词。所谓内部谓词是指用户不必定义就可使用的谓词。内部谓词既可以实现PROLOG无法定义的内容,又为用户提供了常用的谓词,其目的是使程序具有较高的执行效率,最典型的内部谓词是:用来限制PROLOG搜索查找过程的cut操作,cut操作是用户用来告诉PROLOG系统如何改进(加速)搜索或回溯过程,以避免那些不必要的徒劳搜索,从而减少组合爆炸的后果。

如何掌握PROLOG的这些控制过程,这对于写出好的PROLOG程序将是重要的。 2.PROLOG的搜索和回溯

前面,我们已经讲述过,当用户打入一个询问后,PROLOG就到它已取得的事实和规则的数据库中去寻找是否有匹配的子句。其寻找方法是,从上到下地在数据库中找匹配的子句(若找到的匹配子句有多个子目标,则从左到右地逐个去满足)。如果与某一子句匹配成功,询问中的一目标被满足,则PROLOG在数据库中那个匹配子句的地方作一标记,这个位置标记符与该特定目标联系。下面将讨论位置标记符(为直观起见,把它看作为指针)的作用。

本节试图通过两个例子来说明:

①PROLOG是如何对含有多个目标的询问(或多个目标的规则体)进行搜索匹配?②在匹配过程中何时回溯以及如何回溯?③在搜索时对递归定义的规则如何逐层展开?第1个例子侧重说明前两点。第2个例子侧重说明第③点,所以该例子包含了递归定义的规则。

例1设有下列事实和规则: / * | * / likes(tom,talk).

/ * 2 * / likes(robert,swim). / * 3 * / likes(jane,swim). / * 4 * / likes(jane,swim).

/ * 5 * / friend(john,X):-likes(X,talk),likes(x,swim). 现有问题:

?—friend(john,Who)

首先看一下PROLOG的搜索过程:

①PROLOG试图通过满足目标friend(john,Who)来回答问题。首先需要指出,即每个目标保持有它自己的位置记符。设此目标的指针为Pj。根据自顶向下的搜索方法,它找到5行规则,因此指针为Pj指向第5行,同时X与Who共享。

②由于①找到的是一条规则,所以匹配此规则就必须去满足它的两个子目标。于是根据自左向右的原则,PROLOG先去设法满足第一个子目标likes(x,talk),作为一个新的目标,PROLOLG分配另一指针Pt给此子目标,且自顶向下搜索数据库。找到第一行的事实匹配,因此Pt指向第1行,X被实例化为tom,又因X与Who共享,所以Who也为tom。

③在第一个子目标满足后,PROLOG又去满足它的右邻第二个子目标。对它也自顶向下搜索数据库,当它的指针Ps指向第5行之后发现找不到事实likes(tom,swim),设法重新找一个X,这称试图重新满足它。但在这之前首先需要把它在先前被满足时所实例化的变量X脱解为非实例化,即把X的Who和值tom去掉,然后从Pt所指向的第2行向下寻找,看是否有另一子句能满足子目标like(X,talk)。此时找到第3行能满足它,因此指针Pt指向第3行,X被实例化为robert,同样who也为robert,第一子目标满足。

④在第一子目标重新满足后,PROLOG再一次去满足第二个目标likes(robert,swim),它不同于目标likes(tom,swim)。因此PROLOG自顶向下搜索数据库找到第2行匹配,这时Ps指向第2行,这里不妨仍用PS作为目标likes(robert,swim)的指针,这样第5行规则中的两个子目标均满足,因此目标friend(john,Who)与第5行的规则匹配,即目标friend(john,Who)成功,并有:Who=robert

综上所述,在企图满足一目标时可能发生的情况是:

当我们要满足一目标时,从数据库的顶开始搜索,这时有两种可能:

a. 找到一匹配的事实,此时可以说目标已被匹配成功。这目标的指针指向数据库中所匹配的事实,并且实例化先前未被实例化的变量;若匹配一个规则头,则应左到右去设法满足规则的所有子目标。

b. 找不到匹配的事实或规则头,此时说明目标失败,若该目标有左邻,则设法重新满足邻目标,这就是回溯,回到先前一个目标。

当我们设法重新满足一目标时,从这个目标(需要新满足)的指针所指向的地方向下搜索,这时必须把先前在这目标满足时所实例化的任何变戏法量去掉实例化(脱解)。这就是所谓取消先前这一目标所做的所有工作,然后开始恢复搜索数据库,如前面叙述的那样,这个新的被回溯的目标仍可能成功或失败。

下面我们用直观的图示法来描述上述例子的匹配过程。 综上所述,在企图满足一目标时可能发生的情况是:

当我们要满足一目标时,从数据库的顶开始搜索,这时有两种可能: a.找到一匹配的事实,此时可以说目标已被匹配成功。该目标的指针指向数据库中所匹配的事实,并且实例化先前未实例化的变量;若匹配一个规则头,则应从左到右去设法满足规则的所有子目标。

b.找不到匹配的事实或规则头,此时说明目标失败。若该目标有左邻,则设法重新满足左邻目标,这就是回溯,回到先前一个目标。

当我们设法重新满足一目标时,从这个目标(需要新满足)的指针所指向的地方向下搜索,这时必须把先前在这目标满足时所实例化的任何变量去掉实例化(脱解)。这就是所谓取消先前这一目标所做的所有工作,然后开始恢复搜索数据库,如前面叙述的那样,这个新的被回溯的目标仍可能成功或失败。

下面我们给出第2个例子,说明合有递归定义的规则,PROLOG如何逐层展开去满足所提的问题。由于递归便可能产生无限多个可能的解。

例2,用PROLOG定义谓词is-integer

Is-integer(N)的含义是:只要N被实例化为一个整数,则目标is-integer(N)成功;若N未被实例化,则目标is-integer(N)将引起选择一整数,且N被实例化为此整数。

程序如下:

/ * 1 * / is-integer(o).

/ * 2 * / is-integer(X):-is-integer(Y), X is Y+1.

这是一个含递归的程序,如果我们不断地问:?-is-integer(N). /* 当PROLOG回答后用户又打* /,则我们将以递增次序得到所有可能的整数:0,1,2,?,一次一个。实际上每次打“;”就迫便PROLOG回溯一次,表示不满足于上次给出的解。这与例1中的目标不成功的回溯含义略有一点不同。

下面让我们仔细地看一个PROLOG的搜索过程。

①一开始目标is-integer(N)匹配第1行事实,即指针P指向事实1,N被实例化为0,所以PROLOG回答N=0

②当打“;”后,PROLOG企图再次满足目标is-integer(N),于是从P所指向的事实1向下继续搜索,此后,目标is-integer(N)匹配第2行规则头,指向第2行,N与Y共享。

因为第2行是规则,所以必须设法满足该规则体中的两个子目标 is-integer(Y)与X is Y+1.

首先满足子目标is-integer(Y)。作为一个新目标,PROLOG又从数据库的顶开始扫描,找到第1行事实,即子目标is-integer(Y)匹配第1行其实,指针P1指向它,且Y实例化为0。因为是递归,为了区分不同的目标扫描数据库,我们把规则中的变量标以编号。

接着去满足P指向的第二个子目标X is Y+1,此目标成功,且X=Y+1=1,这时整个规则匹配成功,因此目标is-integer(N)也成功,有N=X=1,于是PROLOG回答N=1。

③当继续打“;”,这就意味着希望再找另一个整数,于是迫使PROLOG回溯再次去企图满足目标is-integer(N).这时目标is-integer(N)的指针P指向第二个规则,因为该规则包含了两个目标,因此要使目标is-integer(Y),X is Y+1的重新满足,同上述一样,此时Y将实例化为1,于是有:

X=Y+1=1+1=2

即目标is-integer(N)成功,且N=X=2,因此PROLOG回答: N=2

④同理,再打“;”,PROLOG将回答N=3,读者可模拟一下,看PROLOG是如何继续搜索的。

PROLOG在满足上面目标的搜索过程中,对事实和规则的选择,可用树结构清楚地表现。(如图6-15所示)。

图6-15

如果利用事实1来匹配(解)目标is-integer(N),则不产生更多的目标,如果利用规则2匹配,则另一个子目标is-integer(Y)被产生,于是对此必须做相同的选择。

3.截断(Cut)

在介绍cut之前,先归纳一个目标成功和失败的情况,一个目标成功有三种情况:

①与一个事实匹配。

②与一个规则的头匹配,且该规则体的所有子目标成功。 ③系统的内部谓词执行成功。 一个目标的失败,有四种情况: ①无子句可匹配。

②与一个规则的头匹配,但体执行失败。 ③系统的内部谓词执行失败。

④一度执行成功,而后在执行中发生回溯,又没有别的选择分枝。 Cut是一种能影响PROLOG回溯方式的专门机构,用户可以利用cut来告诉PROLOG,当要回溯前面一串已满足的目标时,就不必再考虑那些目标的其它可选择枝。因为用户事先知道这种不必要的回溯对求解毫无意义。故在程序中合理使用cut,具有两个优点:

事实3(回

答N=2)

其它

事实1(回答N=0)

规则2

1

事实2(回答N=1)

规则3

①程序可以运行得更快,因为它将不会浪费时间去企图满足那些能事先知道不可能导致解的目标。

②程序可以占有较少的计算机存贮空间,因为PROLOG不必为它以后的检查去记录那些对解无帮助的回溯点信息。

从语法上讲,在一规则中利用cut就好象是用一个目标,它的谓词是!但没有变元。作为一个目标它将立即成功,但不能被重新满足,其副作用是改变这以后的回溯工作方式,所谓不能被重新满足,是指当回溯到达到一目标时,不可能再找另一个满足该目标的成功解。换句话说,企图再满足它时引起立即失败,在给出具体例子说明cut的使用之前,让我们先从直观的and-or树中来解释cut的作用。现假设有一棵and-or树,见图6-16。设在匹配子句1时,子目标gl匹配cl成功,子目标g2匹配子句c4成功,cut也成功,但在满足子目标g4时失败,按照PROLOG的正常搜索方法,当子目标g4失败后,将引起回溯,若不存在cut,则首先要求子目标g2寻找另一个可能匹配成功的子句C5。如果g2失败,PROLOG将回溯到子目标gl,对子句C2,C3进行必要的匹配试验。现在因为有了cut,它不可重新被满足且立即失败,因此改变了回溯工作方式,PROLOG将不对C2,C3,C5作进一步匹配试验。这就好象把虚线部分的枝条都已剪除。由此产生从父目标(匹配子句1)到子目标g2之间的所有目标都不可重新满足。在一些PROLOG系统中用符号>未代替!因符号>更能形象地表示搜索只能从左到右通行,而不允许从右到左通行(即回溯)。

下面通过一些简单的例子,未说明cut的三种使用方法。 (1)放在产生器和测试器的后面

Cut的第一种用法是把!放在产生器的后面,典型的模式是:

example(X):-generate(X),test(X),!.

产生器generate(x)不断地产生一些值,每产生一个值,测试器立即测试该值是否满足条件,若不是则回溯到产生器,由产生器产生下一个值再测试,直至找到一个满足条件的解,此时执行到达cut,它指明已找到唯一需要的解,没有必要或不可能再找其它解。

c1 c3 c3 c4 c5 g1 g2 ! g4 子句1 子句2 目标

图6-16

例3,利用加法和乘法未写整数除程序。 divide(N1,N2,Result):-is-integer(Result),

P1 is Result * N2, P2 is (Result-1)*N2, P1=N1,!.

这条规则利用前面的谓语is-integer来不断产生整数,直到产生的数是N2的结果,所以is-integer用作产生器,而剩余的目标起着测试器的作用。由于最后用了cut,因此对给定的值N1和N2,divide(N1,N2,Result)只能对一个可能的值成功。虽然is-integer能够无限地产生许多侯选的数,不过一旦成功地找到结果,cut就终结了任何可能的回溯,若问

?—divide(32,5,X).

X=6; No

因为6*5≤7*5,这里cut的作用是把父目标divide(32,5,X)到cut之间的所有目标(包括is-integer(Result),Pl, is Result*N2>N1)指针都去掉,使得它

们不能被重新满足,因此这种cut说明已经找到唯一正确的解,于是立即停止找另一解的任何企图,或者说终结一个产生和测试序列。

(2)与fail的组合

cut的第二种用法是常与另一个内部谓词fail组合使用,fail象内部谓词cut一样没有变元。Fail作为一个目标总是失败并引起加溯。当fail的前面是cut时,则由于cut的作用将改变正常的回溯方式,使匹配含该cut规则的父目标立该失败(因从父目标到cut之间的所有目标均不可重新满足)。cut和fail的组合使用在实际应用中是很有用的。

下面讨论如何在一程序中利用这种组合来描述一个人是健壮的。

例4,我们要描述一个人是健壮的,条件是,如果他没有心脏病,没有肺病,不是近视眼等等。

也许我们开始会写出规则: strong(X): -heart-disease(X),fail. strong(X): -tuberoulosis(X),fail. strong(X): -bearsight(X),fail. strong(X).

第1条规则企图说,如果X是有心脏病的人,则匹配它的目标将失败,同样,第2,3条规则说的是,若X有肺病或近视眼,则他不是强壮的。而第4行的子句则企图说如果X没有诸疾病,则他是强壮的。但上述程序是不正确的,假若问:

?--strong(wan-lin)

用假设已存在一事实heart- disease(wan-lin), 则我们本来希望询问中目标匹配第一条规则后得到回答no,但由于匹配第1条规则时,目标heart—disease(X)成功后接着遇到目标fail,它总是失败,于是引起回溯。这样造成PROLOG的因溯功能引起的结果。为了停止PROLOG这种不必要的回溯,可以用cut放在fail之前完成。于是改写为:

strong(X):-heart—disease(X),!,fail. strong(X):-tuberoulosis(X),!,fail. strong(X):-nearsight(X),!,fail. strong(X).

现若询问?—strong(wan-lin).

则上述目标匹配第1条规则后,因为谓词fail马上失败,又因为cut使得从父目标strong(wan-lin)。到cut之间所有目标都是不可重新满足的,因此PROLOG回答no。由此可见,因为cut和fail的作用,改变了正规的回溯方式,提高了搜索速度。最后一条规则是匹配任何一个strong(X)目标,该目标已经过了上面各种情况的排除才与最后一个规则匹配,所以此人一定是健康的。这个例子中的cut用法也称作cut-fail组合法或cut的排除型选择用法。

(3)分情况选择

Cut的第三种用法称为分情况选择,这种用法更为普遍,它是针对下列情况使用。

当情况1→ 做动作1 当情况2→ 做动作2 :

当情况N→ 做动作n 否则 → 做动作n+1 样本写法将是:

sample(case l, X,?): -!,acctionl(X,?). sample(case 2,X,?): -!,action2(X,?). sample(case n,X,?): -!,actionn(X,?). sample(others,X,?):-cath-actionnl(X,?). 这里cut肯定了分情况选择的,不可能再有第二种。

例5,定义谓词sum-to,使得目标Sun-to(N,X)在N为一整数值时,X将被实例化为从1到N的和,例如:

?—sum-to(5,X).

X=15; / * 注意这里打分号* / - - - - - No

因为1+2+3+4+5=15,所以X为15。显然这是递归程序。终结情况为N=1,否则先求出1到N-1之和,再加N,于是sum-to的程序为:

Sum-to (1,1):-!

Sum-to (N,Res)):-Nl is N-l, sum-to(N1,Resl),

Res is Res1+N.

终结条件是N为1,此时答案也是1,第2个子句引入另一个sum-to目标,这个新目标的第一个变元的值比原来小1,如此递归下法,直至到达终结条件,需注意,不能写Sum-to (N-1,Resl).

代替N1 is N-1,sum-to(N1,Res1).

需说明这个例子为什么在第1个子句中用cur?从前面处理表的一些例子中,大家会看到它们的第1个子句通常处理空表,其后并不使用cut,这是因为表的处理,容易区分空表[ ]和非空表[A|B]两种情况。因此,在定义有关表运算的谓词时,只需提供上述两种不同模式的规则头分别处理就可以了。某对象匹配空表就不会匹配[A|B],反之亦然(即它们的交集为空)。但是对于数,并不容易区分。因为我们不可能指明一个模式,它只匹配不等于1的整数。于是这个例子所采用方法是:提供一个模式加cut针对整数1的情况。而用变量来匹配其它情况,从PROLOG的搜索方式知道,对任一目标首先试图匹配该目标谓语的第1个子句(这里检查N=1?),如果第1子句失败再试匹配第2个子句等。对这个例子,第2个子句应只匹配不等于1的数。但问题在于第2子句也能匹配数1,如果第1子句中不用于cut,即若写成:

sum - to(1,1).

sum - to(N,Res):-N1 is N - 1,sum-to(N1,Resl),

Res is Resi+N.

当用户询问?— sum—to(1,X)时,第一次得到正确的解X=1,但若“;”和回车换行后,PROLOG进行回溯重新选择子句匹配时,将找到第2个子句,这就产生错误。实际上将引起无限运行,直到存贮空间用完。

读者可以模仿上节递归展开的例子,分析一下打“;”后为何会发生这种情况,但若cut加在第1个子句中,则对目标sum-to(1,X)只能成功一次,PROLOG决不试第2行规则。

这个例子说明,当我们不能够通过指明规则头的不同模式来区别所有可能的情况时(交集不为空),可以利用cut让PROLOG在搜索时选取确定的规则。这是

cut是一种用法,称为确认一规则的选择。但上面sum-to谓词实际上是不完善的,因为对目标sum-to(M,S),当M≥1时程序是可行的,而当M小于1的整数时,也会出现无限运行的情况,为避免出现这种情况,为避免出现这种情况,可把sum-to改写成:

sum-to(N,1):-N=<1,!.

Sum-to(N,R):-N1 is N-1,sum-to(N1,R1),

R is R1+N.

此时,如果提供的N小于等于1,则选择第1个规则,虽然对小于1的N给出了不妥当的结果,但不会出现无限运行的情况。

此外还可以用内部谓词not来改写上述程序。内部谓词not定义为,若X作为一目标失败,则目标not(X)成功,若X成功,则not(X)失败。于是可以用not来代替cut。

sum-to(1.1).

sum-to(N,R):-not(N=1),N1 is N-1,

sum-to(N1,R1),R is R1+N.

同样:

sum-to(N,1):-N=<1.

sum-to(N,R):-not(n=<1),N1,is N-1,

sum-to(N,R1),R is R1+N.

事实上,上面两个not可直接用N\\=1和N>1来分别代替。

用not来代替cut是一个好的程序设计风格,因为含有cut的程序通常比较难读。但如果not中条件相当复杂,则下列这种一般形式的程序将比用cut低。

A:-B,C. A:-not(B),D.

因为若匹配第2个规则,则PROLOG常试图满足B两次(一次在第1子句,一次在第2子句)。若B是一复杂的条件,显然是费时的,而若用cut,则具有同样功能的下列程序段将有效得多。

A:-B,!,C. A:-D.

因此人们有时必须在程序的易读性和效率性上作出选择。现改写例4,用not

代替(cut-fail)组合,程序需要重新组织。

srtong(X):-not(heart-disease(X)),

not(tuberculosis(X)), not(nearsight(X)),?。

Turbo PROLOG语言

一、概述

Turbo PROLOG语言是美国Borland公司于1986年推出的适用于IBM PC及其兼容机的典型的PROLOG编译程序。它不仅保持了clocksin—Mellish所描述的PROLOG语言的主要特点,而且比解释型PROLOG的运行速度快得多,从而使得微机上的PROLOG语言可以在人工智能的许多领域得到广泛地应用。

本节主要介绍1988年推出的Turbo PROLOG 2.0版,它在原来的1.0和1.1版的基础上作了较大的改进和扩充。下面概括一下Turbo PROLOG语言的主要特征。

①Turbo PROLOGR提供了既安全又方便的程序开发环境。同时能对源代码进行跟踪调试。

②运行速度快,内存需求少。

③提供了丰富的内部谓词集,可以利用窗口、图形、声响等内部谓词编写图文并茂的程序。

④既可支持多重内部数据库,又具有强有力的外部数据库系统,能对磁盘或扩充存储器上的大型数据进行快速存取。

⑤系统内部配有完整的整数,实数算术运算操作及科学计算函数。 ⑥Turbo PROLOG是一个集成式、模块化的程序开发环境。可以将多个程序模块联结成一个可执行的程序。还能够同C,PASCAL和汇编语言书写的模块联结成一个可执行的程序,并提供了和这些语言之间交互的接口。

⑦提供了支持元程序设计的Turbo PROLOG解释程序的源代码和注释。可对该源代码进行修改,以便处理新的逻辑程序语言。

由于上述特点使得Turbo PROLOG成为微型计算机上一个较为实用的PROLOG系统。它可用在IBM PC/XT,AT,286,386,486或其兼容机上,并适用于以下

广泛的应用领域:

①Turbo PROLOG可以快速地实现任一实际应用系统的原型。 ②可以完全联接PC机的I/O端口,可以进行监督生产或实时控制。 ③可以实现动态关系数据库处理。

④能进行符号处理,构造符号微积分等软件包,并可以进行语言转换,实施机器翻译,实现自然语言理解。

⑤利用Turbo PROLOG的演绎推理功能,很适合构造定理证明等各种人工智能软件包。

⑥利用和其它语言的接口,实现和现有软件的交互。可充分利用原有的软件资源。

⑦构造专家系统或专家系统开发工具。

二、程序结构

Tubo PROLOG语言的语法和clockisn-Mellish定义的PROLOG有明显的不同,Turbo PROLOG语言更加结构化并采用强制域类型检查。语法的改变主要是迎合编译技术的需要。

1.名

Turbo PROLOG中,名用来表示符号常量、域、谓词及变量。一个名由一个字母或下划线,后 零个或若干个字母、数字及下划线组成。对名有下列两点重要的限制:

①符号常量名必须以小写母开头。 ②变量名必须以大写字母或下划线开头。

除了上述两点限制以外,在程序其他地方可随意使用大小写字母。例如,为增强可读性,可混合使用大小写字母,例如:

MyLongestVariableNameSoFar

或者使用下划线:

pair_who_might_make_a_happy_coupte(henry_viii,ann_boleyn) (1)关键字

下列关键字作为系统的保留字,它们不允许用作用户自定义名。 and domains goal include

clauses elsedef if or

constants enddef ifdef predicates database global ifndef

(2)特殊谓词

下述谓词由编译程序作特殊处理,不要在程序中重新定义这些名: assert chain_terms format retractall asserra consult free save

asserz db_btrees not term_replacea bound db_chains readterm trap chain_inserta fail ref_term write chain_insertafter findall retract writef chain_insertz

2.程序段

Turbo PROLOG程序由若干程序段组成,每一程序由一关键字来标识,如下表示:

表6-1 程序段内容

段 编译指令 constants段 domains段 database段 predicates段 goal段 clauses段 内容 编译指令出现在程序开关 零个或多个常量定义 零个或多个域说明 零个或多个数据库谓词说明 零个或多个谓词说明 零个或一个目标 零个或多个子句 用户编写的程序不一定要包括上述的所有段。然而一个程序也可包含多个domains,predicates,database或clauses段,但须遵守如下几条限制。

①常量、域及谓词应该先定义后使用。不过,在domains段中可引用后面说明的域。

②在编译程序中只能满足一个目标。该目标可出现在说明其子目标的domains段后的任一地方。

③所有描述同一谓词的子句必须依次(一个接一个)出现。 ④所有全局说明必须在局部说明之前。

⑤可对database段命名,但给定的名只能出现一次。因为默认名是dbasedom,故只能有一个未命名database段。

下面讨论各个程序段的内容和描述形式。 (1)领域段

领域含有领域说明,有四种模式可以采用: ①标准域

name=d

该说明定义了一个领域name,其元素类型是标准类型d. d只能是integer,char,real,ref.string,symbol.该说明用于定义语法上相同而语义不同的对象。

②表域

mylist=elementDom *

这是定义表域的一种简便方法。Mylist是一个表组成的领域,其中元素来自领域elementDom .领域elementDom既可以是用户定义的领域,也可以是标准域类型。说明中的星号“*”读作“表”,例如,领域说明

Mumberlist=integer * 定义了一个整数表的领域。

③复台对象域

myCompDom=f1(d11,?,dle);f:(d21,d22,?)?定义一个复合对象组成的领域,且说明了一个函子和所有子元素的领域。

例如,现定义一个域owners,其元素形式为: owns(john,book(wuthering-heights,bronte)) 我们可如下定义:

owners=owns(symbol,book) book=book(symbol,symbol)

此处owns是复合对象的函子,而symbol和book则是子元素的领域。

这种领域说明的右边可定义几个不同的复合对象,其间用分号(;)或关键字分开,每一复合对象必须含唯一的函子及其子元素的领域说明。

④文件域

file=namel;name2;?;nameN

若想用符号名(非预定义名)来访问文件,则必须定义文件域。一个程序只能有一个该类型的域,且须以file标识。给出的堵符号文件名则作为file域的可选文件名。

下列可选文件名是在file域中预定义的: keyboard stdin soreen stdout printer stdert (2)谓词段

Turbo PROLOG中,以关键字predicate开头的段是用来说明谓词的。说明谓词时应指出谓词名及参数的领域,如:

predname(domain1,domain2?,domain)

其中predname是谓词名,而domain1,domain2?,domainN表示用户定义的领域或标准 型领域。

谓词亦可仅由名字构成。例如,可以用如下的规则来定义谓词choose-teams:

Choose_teams:_smae_league(X,Y),never_played(X,Y),write(X,Y),其中,谓词choose_teanis就不带参量。

允许对同一谓词作多次说明。例如下面谓词member说明以数和名字均有效: member(name,namelist) member(name,numberlist)

其中,name,namelist,number和numberlist都是用户定义的领域。在对同一谓词作多次说明时,参量个数必须相同,但不要求具有一样的参数。

(3)子句段

子句是与已说明过的谓词相应的事实或规则,通常一个子句可以是: ①一个事实。

②一个子句头,后跟冒号再加连字符(:—),以及一张谓词调用表,其中谓词由逗号或分号隔开。

不论是事实还是规则都必须以句点结尾。其中有些符号可用下面符号代替;

符号“:-”可用关键字if代替。 符号“,”可用关键字and代替。 符号“;”可用关键字or代替。

例如,Turbo PROLOG事实same-ieague(ucla,use).

是由单一原子(本身为一名字)same-league和一个用括号括起来的项表(ucla,use)构成。

项可以是(简单)常量、变量或复合项,现分别详细地说明如下: ①简单常量(simple constants)

简单常量是属于字符领域、整数领域、实数领域、串领域、符号领域和文件领域六种标准类型领域之一:

字符(character):属于字符类型领域(两个单引号括起来的8位ASCII字符可以用一个换码符(ESCAPE Symbol)即反斜线(\\)后跟一个ASCII代码来标识。例如,\\n和\\t分别产生一个新行字和一个制字符。反斜线后紧跟的任一其它字符均产生那个字符本身。

整数(integer):属于整数领域类型,其取范围为-32768到32767间所有整数。

实数(real):属于实数领域类型,其取值范围为±1E-307到±1E308。实数的书写格式为一个正负号后接带(或不带)小数点的尾数部分,再接由一个正负号、E和指数组成的指数部分,格式中均不能有空格。格式中的正负号、小数点、指数部分均是可缺省,必要时,整数将自动转换成实数。

串(string):属于串领域类型(是由双引号括起来的任意的字符序列)。字符串中可以包含由上面所述的ESCAPE序列即反斜线所产生的字符。

符号常量(symbolic constant):属于符号领域类型(是以小写字母开头的名字)。串也可用为符号,但符号要保存在一张查找表格中以便快速匹配。符号表格的缺点是造表需要耗费时间和占用存储空间。

符号文件名(symbolic filename):属于文件领域,是以小写字母开头的名字,出现在文件领域说明的右边,或是预先定义的符号名:printer,screen,keyboard和coml等。

②变量(variables)

变量是以大写字母开头的名字,至于无名变量,可用一个下横线表示,若对

变量的值不感兴趣,则可用无名变量表示之,未同其它项结合的变量称这为自由变量,实例化后就称为约束变量。例如,已同一个项合一后的变量即为约事变量。谓词feen(X)成功的条件是:变量X已约束为一个值。

③复合项或结构(compound terms or structures)

复合项或结构是一个由称为描述名的函子和一组称为成分的其它对象组成的单对象。这些成分括在圆括号中且两两之间用逗号隔开。函子写在左圆括号之前。

例如,下述复合项由函子author和三人成分构成: author(fmily,bronte,1818)

复合项属于用户定义的领域。对应复合项author的领域定义有: domains

authors=author(firstname,lastname,year_of_birth) firstname.lastname=symbol ④表( lists )—一种特殊的复合项

表是 Turbo PROLOG中一种常用的数据结构,实际上它是复合对象的一种形式,表由括在方括号中的若干项组成,项之间用逗号隔开。

例如,[1,2,3.8,—3.2]是一张整数表,它属于用户定义的领域。 即:

domains list=integer

当表中元素是多种类型,则这些类型都必须同时分别说明。对于下面的说明:

domains

element=c(char);i(integer) list=element*

所允许表的形式是:

[1(12).1(34),C(‘X’),C(‘Y’),C(‘Z’),i(987)] (4)数据库段

数据库段象谓词段一样说明谓词,但在数据库段中的谓词仅能包括无变量的事实,这些事实在运行时 自assert,asserta,assetrz及consult插入,并可由retract或retractall删去,在程序中可以有多个数据库段,有些是全局的,

有些是局部的,对程序的数据库段应给一个名字,并且在该模块中每个名字必须不同,若对数据库段不命名,编译程序将赋予它一个默认名dbasedom.

如果已知一个谓词只对应一个事实,则可以在此数据库谓词前加determ,这样使得编译程序产生更高效的代码,并且在词用此谓词时不会出现不确定性警告,这对于标记(flags)计数器以及其他全局变量来说是很有用的。

说明了一个数据库段后,编译程序在内部将使用与此数据库段相同的名说明一个与之对应的 。这便得谓词能象处理项一样处理事实。

(5)常量段

常量说明段由关键字constants指示,后跟说明本身。 语法如下: =<宏定义>

每一个定义以一个换行符结尾,因此每行只有一个常量说明,按这种方式说明的常量以后可在程序中使用,例如:

constants blue=1

language=english

Turbo PROLOG会自动用每个变量实际对应的字符串替换之。 使用符号常量有以下限制:

①一个常量的定义中不能引用其自身,例如:

list=[1,2][list] \\*不允许*\\

将产生错误信息:“常量定义中出现递归”。

②在常量说明中系统不区别字母的大小写,因此当一个常量标识符在程序中的子句段使用时,其首字母应小写以避免与变量混淆。

③在一个程序中有多个常量说明段,但每个常量必须先定义,后使用。 ④常量标识符对文件的剩余部分而言是全局性的且只须被说明一次,同一标识符的多次说明会产生错误信息。

(6)目标段

以关键字goal开头的目标段允许用户标识一个固定的目标,即对PROLOG程序的询问,如果用户想在内存中试各种不同的目标,可以省去目标段。运行时系统将在对话窗口提示(goal:)输入目标,这种方式类似于解释型的PROLOG

系统,但在编译成执行文件的程序中必须包含一个非空的目标段。

目标和规则体在本质上是相同,目标也可以是一子目标序列。 3.编译指令

Turbo PROLOG提供了一些编译指令,程序员可以通过编译指令来控制编译程序以特定的方式处理程序代码,下面列出Turbo PROLOG2.0提供的编译指令。

bgidriver config nobreak shotttrace bgifont diagnostics nowarnings trace check-dererm errorlevel printermenu trail code heap project

许多编译指令既可以从Turbo Prolog开发环境(菜单)中设置,也可以在源代码中设置。源代码中的编译指令,必须出现在程序正文的开始处,并可能置盖由菜单设置的该编译指令的值。

下面分别介绍各类编译指令的用途、用法。 (1)bgidriver

用于把某一特定的图形驱动程序直接连入用户的可执行的图形程序。后跟图形驱动程序文件的公共名。

用法:bgidriver“GGA-driver-far”

其中,GGA-driver-far是希望使用的图形驱动程序文件的公共名。 (2)bgifont

用于把BGI的笔划式字符字体连入可执行的用户图形程序。 用法:bgifont“_gothic_font_far” (3)check_determ

如果使用了check_determ,Turbo PROLOG系统就会对导致一个非确定谓词的每个程序子句给出一个警告。

(4)code

编译指令code说明了内部代码区的大小,该指令缺省则表示代码数值是16KB。code的用法是:

code=Number_of_paragraphs

其中Number_of_paragraphs为代码区所要的段数,每段16B,例如,指令:code=1024表示代码区为16KB。Code命令对EXE文件的大小无影响。

(5)config

为了使独立执行的EXE程序读入定义了默认的窗口属性,键盘设置的SYR文件可在源程序设置

config“.sys”

编译指令,生成的,EXE程序执行时读入.sys,并可获得尤如Turbo PROLOG使用PROLOG.SYS一样的效果。

(6)diagnostics

使用了diagnostics编译指令,编译系统会显示出程序的分析信息,包括: · 使用的谓词名

· 谓词是局部的,还是全局的,抑或外部定义的 · 谓词是确定的还是非确定的 · 每个谓词的代码大小 · 参数的领域类型 · 每个谓词的流模式 (7)errorlevel

errorlevel编译指令可用来控制EXE文件错误报告的详细程序。用法是: errorlevel=d

其中d为程序报告的级别,可为0,1,2之一,其中

0:产生最高效的代码,这同Turbo PROLOG的1.X版本中的策略一致。 1:产生可以缺省的。当出现错误时,会显示出一些原始信息。

2:在这一级,能报告一些在级别1中不报告的错误,例如找滋出、堆滋出、尾区滋出等。

(8)heap

heap用于指出EXE文件由DOS启动时应占据多少存贮空间,如果不使用这个heap命令,或将它的值置0,程序便可占据一切可利用空间,heap指令的用法如下:

heap=Number_of_paragraphs

(9)nobreak

如果nobreak命令缺省,Turbo PROLOG系统将生成一些代码用以在调用每个谓词之前测试键盘,以确认没有按过Ctrl-Break复合键。这样做稍降低了程

序执行速度,存贮开销也大些。使用nobrdak可以防止这种代码的自动产生。但给出了nobreak编译指令后,跳出死循环的唯一办法是重启操作系统。因此,nobreak指令只当程序彻底测试后方可使用。

(10)nowarnings

程序中使用nowarningw编译指令,当一个变量在子句中仅出现一次时,可抑制警告信息的轴出。

(11)printermenu

若这条指令出现在程序中,则用户能把屏幕输出送到打印机或将其记到LOG文件。

(12)project

project编译指令用于模块化程序设计。一个工程中的所有Turbo PROLOG模块需要共 内部符号表。例如,有一个称为MYPROJ的工程,符号表可以放在名为MYPROJ.SYM的文件中,使用时,project指令必须出现在模块的第一行以说明该模块所属的工程。

下面一行代码表明该模块属于MYPROJ project\” (13)trace(shorttrace)

Turbo PROLOG中的跟踪方式有好几种,最简单的方法就是在程序中引入编译指令,可把trace或shortrace指令放在源程序的顶部。

trace指令将报告作为程序执行逻辑组成部分的每一个调用的返回;而shorttrace只考虑Turbo prolog执行的特定优化方式,并且只报告调用和返回的一个子集。

如果在程序的首部使用了trace或shorttrace将跟踪所有的谓词,如果trace或shorttrace后跟一个谓词名表,则仅对表中所列谓词跟踪。

(14)trail

Trail指令说明内部尾数组的大小,使用格式是:

Trail=Number_of_paragraphs

尾数组用于记录副作用(主要是指针变量的约束)。默认情况下,无尾数组,若在程序中使用指针变量,则必须明确给出尾数组大小,不然将产生尾区溢出错。对大多数情况,设置trail=100就足够了。

Prolog教程

第一章 入门

如果你是一位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中表示“如果”的意思,我们使用它来定义规则。上面这句话的意思就是,

本文来源:https://www.bwwdw.com/article/ccp3.html

Top