台式pc做外接音源接口 usb的话搞usb声卡和dac的区别有多

prolog&第二篇
3.1递归的定义
谓词可以采用递归的形式进行定义,不严密的说,使用递归定义的谓词就是在规则的定义过程中至少要有一条规则要去引用它的自身。
Example 1: Eating 例子1 吃东西。
看一下,下面的知识库:
is_digesting(X,Y) :-
just_ate(X,Y).
is_digesting(X,Y) :-
just_ate(X,Z),
is_digesting(Z,Y).
just_ate(mosquito,blood(john)).
just_ate(frog,mosquito).
just_ate(stork,frog).
初看起来,它很是普通:这不就是一个含有 3个facts 和 两条 rules 的知识库吗?不过,is_digesting/2 的定义采用的是递归形式。注意看下:在&is_digesting/2& 的定义中,有一部分是它的自身,因为,&is_digesting/2 这样的functor 不仅在规则中出现,它还出现在了规则体里面。不过,在这个闭环里面它还是有一个逃逸出口的。这个出口也在谓词
is_digesting/2 的定义中给了出来,第一条规则不就是吗。(这个是非常的明显,第一条规则的规则体(右半边儿)可是与digesting/2 无关的。)
Declarative ,直白的声明,这个词一般用在表示Prolog&knowledge
bases的逻辑含义。也就是说,一个知识库的Declarative meaning (直白意义)的意思,是表达
它说了些什么。或者,当我们读到那样的一些逻辑句子(a collection of logical
statements)后所能够理解到的意思。好了,我们这个递归式的定义,里面的
Declarative
meaning (直白含义)是相当的简单。第一个子句式,(逃逸句式
the escape
clause,也就是那个没有递归的,或者,我们也常常称它为,基础句式base clause),说的很简单:要是X刚刚吃掉了Y,那么现在X就正在消化Y。这句的含义是非常的明显。那第二个局式呢?那个递归的句式,它说:要是X吃掉了一个Z,而那个Z又正在消化一个Y, 那么 X现在也在消化Y。这个句式的意义也是很明显直白。
很有理由,我们再做进一步讨论:基础规则(base rule&)它和我们先前见过的规则没什么不同,它的意思是,要是,我们问:X是不是正在消化Y 的话,Prolog 可以 转而去问:X她把Y给吃了吗?这样转换一下作为替代。
那个递归句式该怎么办呢?这里再给出一条Prolog用来确定X是不是正在消化Y的策略,Prolog会尝试去找出几个X刚吃过的Z,这些Z呢需要正在消化Y。也就是,这一条规则呢,它会把一个任务拆分成了两个子任务:它之所以这样做就是希望,最终会得到(lead to&)一些非常简单的问题,而这些问题的简单性就在于:只需要傻傻的(简单的)在knowledge base.知识库里面,扫描比对一下就可以直接发现答案。下面的这幅图总结出了这样的情况:
<img WIDTH="554" HEIGHT="59" ALT="*Pic not found*" V:SHAPES="图片_x0020_1"
TITLE="prolog&第二篇" />
我们在一块来看一下 它到底是怎么完成的。要是我们pose 一个这样的请求:
is_digesting(stork,mosquito).
Prolog随后的工作就像下面这样:
首先,她会去使用(知识库的)规则清单里面第一个与is_digesting有关的那一项。即:第一项,规则说:X正在消化Y 的前提是
X刚吃了Y。通过把X unify 归并成stork&
Y unify 成mosquito&,我们得到下面的目标:
?- just_ate(stork,mosquito).
不过呢,知识库里面 没有找到有&鹳
的信息。所以这个想法/企图落空了。
那么,接下来,prolog 就需要使用第二条规则了,它把X unify 成了
之后,它得到了这样的两个目标:
?- just_ate(stork,Z),
is_digesting(Z,mosquito).
也就是,想要 给出 is_digesting(stork,mosquito)&的判定,Prolog 需要找出Z的值,Z的这个值需要:
首先满足:
&&&&&&&& ?- just_ate(stork,Z).
其次还要满足
&&&& ?- is_digesting(Z,mosquito).
结果,还真有一个这样的值给Z,它叫青蛙 frog。
那就立马,检验一下:
?- just_ate(stork,frog).
这一定是会成功的,因为这个事实 fact 就直接罗列在知识库之中,然后在规约:
?- is_digesting(frog,mosquito).
他也是很容易的,应为第一项&is_digesting/2句式的规约结果就是:
?- just_ate(frog,mosquito).
而,这不就是作为一条fact 而列示在&knowledge base.中的吗?
好了,这就是我们第一个有关递归的例子。我们接下来还要学习有关递归的更多知识。还有,一个非常有实践意义的重要忠告该要立马给出来了:希望你能够,在你写出递归式的谓词predicate 的时候。它至少该有两个子句式,一个是基础句式(这个句式要给出几处可以终止递归活动),另一个就是含有递归的。要是,你没有这样做,prolog 就会以螺旋式进入一中永无终止的并且毫无意义计算序列,例如,这就是一个非常简单的递归式的规则的定义。
就是这个样子,它的美丽至于在于它的简单,如果从一种
declarative
perspective 直白语义的角度,他是一个意义非常明白的定义(以至于好没意思),它说:要是p 代表了某种意义,那么
p 所代表的。这个你不该会有异议吧。
不过,要是以一个过程式角度,这是一条无比危险的的规则。实际上,我们这里有了一个递归规则的终极危模式:两边是完全一样的,还有,没有一条基础规则可以从递归之中退出来。比如,考虑一下,我们pose 一个这样的query:
Prolog 这样问自己: 我要怎么去证明p呢?,随后,它又意识到,我这不已经有了一条规则吗?要证明p我只需要去证明p 就可以了。结果,他又再一次问了自己:我要怎么去证明p呢?随后,它又意识到,我这不已经有了一条规则吗?要证明p我只需要去证明p 就可以了。结果,他又再一次问了自己:我要怎么去证明p呢?随后,它又意识到,我这不已经有了一条规则吗?要证明p我只需要去证明p 就可以了。结果,他又再一次问了自己:我要怎么去证明p呢? 。。。 就这样的,无休止的
进行下去。。。
要是你 发出了一个这样的query,prolog 是不会回复你的。它会启程至(head off) 一个不会终止的绝望回环。也就是,它停不下来(terminate),这样了,你就得,不得不去打断他。当然,要是你使用
跟踪话,你每次都是一步接着一步的来,这样直到你对Prolog的这种回环检测感到了厌倦。
Example 2: Descendant 后代-后裔
现在我们已经知道了在prolog 的递归中究竟包含了什么。是时候
解释一下它的重要性了(&it is time to ask&why&it is so
important.)。事实上,这个问题可以在很多层面上进行回答,不过,就现在而言,让我们更多的关注它在实践中的意义(keep things fairly practical)。所以,在我们着手写出一段有意义的prolog 程序的时候,递归定义当真就那么的重要?,要是当真如此,那又原因何在?
我们一块来思考一下这个例子,加入我们有一个这样的知识库,里面记录着孩子的信息(关系)。
child(bridget,caroline).
child(caroline,donna).
也就是:凯洛琳
布丽吉 Bridget
的孩子,多娜 donna 是 caroline
的孩子。现在呢,我们想要定义一个后代关系,也就是这种关系需要时:是谁的孩子,是谁的孩子的孩子。是谁的孩子的孩子的孩子。这是我们第一次有要处理这种问题的想法(attempt&),我们可以把下面的这个两个非递归的规则加入到知识库:
descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
&&&&&&&&&&&&&&&&
child(Z,Y).
现在呢,这是相当的明显,这种定义是在孩子关系上增加了一点(fairly obviously these definitions work up to a
point)。不过,还是相当的有限:这种定义只是给出最多两代的后代关系。对于上面的这个知识库,倒是没问题。要是,我们我们的知识库里面的孩子关系的信息多起来,我们会把我们的
孩子关系的事实信息
&child-of facts&
扩展成这样:
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
现在,我们的规则,就不够了,比如要是我们pose 请求:
descend(anne,donna).
descend(bridget,emily).
我们得到的回答是:no,这可不是我们想要的,当然我们也可以再加的规则,像下面这样:
descend(X,Y) :- child(X,Z_1),
&&&&&&&&&&&&&&&&
child(Z_1,Z_2),
&&&&&&&&&&&&&&&&
child(Z_2,Y).
descend(X,Y) :- child(X,Z_1),
&&&&&&&&&&&&&&&&
child(Z_1,Z_2),
&&&&&&&&&&&&&&&&
child(Z_2,Z_3),
&&&&&&&&&&&&&&&&
child(Z_3,Y).
但是,我们需要正面来面对它,这是种笨拙的方式,还非常难懂(clumsy)。我们会发现,在我们的 孩子关系的事实信息
&child-of facts&
不停的开始增长的时候,规则就要变成这样了:
descend(X,Y) :- child(X,Z_1),
&&&&&&&&&&&&&&&&
child(Z_1,Z_2),
&&&&&&&&&&&&&&&&
child(Z_2,Z_3),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&
child(Z_17,Z_18).
&&&&&&&&&&&&&&&&
child(Z_18,Z_19).
&&&&&&&&&&&&&&&&
child(Z_19,Y).
这样做此类事情的方法恐怕是不能有特别的好的心情吧。
不过,我们大可不必这样了。我们可以完全不用再把规则写的更长了,下面的这个递归式的谓词定义,解决了我们所有的问题,它就是我们想要的。
descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
&&&&&&&&&&&&&&&&
descend(Z,Y).
那么,它说了些什么呢?基础子局式就是说:要是Y是X的孩子,那么Y就是X的后代,意义相当明显的。那么,那句递归的子局式呢?他的直白含义就是:如果,Z是X的孩子,并且Y是Z的后代,那么Y就是X的后代。这也是,显然就正确的。
那么,让我们再看下这个递归式谓词的过程语义(procedural
meaning),在这个例子里我们是分步来进行的(by stepping through an
example.)。看一下,在在我们pose 完请求之后 究竟发生了些什么:
descend(anne,donna).
Prolog 先是尝试使用规则1,它把 规则头部 里面的X归并成anne&,Y归并为donna&
之后,接下来Prolog会去证明这个目标:
descend(anne,donna).
这一回子,它失败了。因为,知识库里面 既没有包含有 fact&事实
child(anne,donna)&,也没有哪一条规则
可以推断出
这样的形式。所以,Prolog 要倒退(backtracks)回来,它还要继续地再次去找出另外的一条路径(an alternative way)来证明:descend(anne,donna)&,它把知识库里面第二条规则找了出来,那么,现在我们又有了另外的一个目标:
child(anne,_633),
descend(_633,donna).
先考虑子目标里面的第一项,并尝试用一些知识库里面的内容去归并它。它找出了事实:act&child(anne,bridget) 并把变量_633 并实例化成bridget&,现在,子目标的第一项满足了。Prolog 移动到第二项,它还必去证明:
descend(bridget,donna).
这是第一次的递归式调用predicate&谓词
descend/2&。就像从前一样,Prolog 从第一个规则开始,不过,失败了,因为目标
child(bridget,donna).
是无法证明的。退回去(Backtracking),prolog 还找到了第二种可能性去来检测:descend(bridget,donna)&,就叫它规则二。这就再一次的为prolog 提出了两个子目标:
child(bridget,_1785),
descend(_1785,donna).
知识库里面的事实
&child(bridget,caroline) 来归并。也就是
变量 _1785&
是用caroline&来进行初始化的。接下来,Prolog 就要去证明:
descend(caroline,donna).
这就是谓词:predicate&descend/2的第二次调用。和前面的一样,它要先去
尝试第一条规则。结果呢,就获得了下面的这一条目标:
child(caroline,donna).
这一次,Prolog 办成了,因为
&child(caroline,donna) 是一条在知识库里面的
事实fact. &这样Prolog为目标:descend(caroline,donna) (第二次的递归式调用)找出了一条有理由充分的证据。还有,而这也意味着,descend(bridget,donna)&(第一次的递归式调用)也是正确的。这就是说,我们原来的query 请求
也是正确的。
这里有一棵
query/请求
&descend(anne,donna) 的 检索树(search tree&),而这会让你明白上面的文字里的讨论与这棵树之间是有着某种对应关系的(&Make sure that you understand how it relates to
the discussion in the text)。也就是说,没当需要去判定、证明
一条query请求的时候,
就会遍历一棵这样的
<img WIDTH="554" HEIGHT="439" ALT="*Pic not found*" V:SHAPES="图片_x0020_2"
TITLE="prolog&第二篇" />
从这个例子中,可以明显的发现,无论是我们再增加多少代子孙(孩子),我们还是可以做出
后代关系(descendant relation)的判定,也就是
递归式定义
不仅能够体现通用性,它还具有简洁和紧凑(compact)性
它包罗了所有的非递归式规则的信息,并且还有更多。非递归的规则定义,只能定义出一些固定数量
的 后代关系
概念。要是我们想要把
的这种概念
全部地都给表达出来的话,如果用非递归的方式我们就得无限的写下去,当然这是不可能的。不过,这就恰巧是递归式的规则所能为我们做的。它把我们需要去处理(&cope with)那些
任意(arbitrary)数量的(numbers&)的
代级关系(generations)都给捆了起来,捆成了那区区的3段代码。
递归性式规则
那可是真的是很重要,它让我们可以把大量的信息包裹起来,以一种更为紧凑的形式,运用一种更为自然的手法来
定义谓词(They enable to pack an enormous amount of
information into a compact form and to define predicates in a
natural way)。作为一名Prolog 程序员你工作中的绝大多数
就是使用递归来表达你的规则定义。
Example 3: Successor 例子3 ,后继
在前面的章节中,我们说过(remarked&),使用归并来建立结构(building structure through
unification)在prolog 中是一种重要的思想。而现在我们又明白了递归,至此,我们就可以做一些关于他们的更加有趣的表演(&illustrations of this)。
当今,当人们在使用(&write numerals)数字的时候。一般上,大家用的是10进制的表示法。(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
等等),不过,你还可能知道,还有很多其他的计数方式。正如,计算机硬件是以数字电路(digital circuits)为基础的,计算机呢一般是采用二进制来表达数字的(0, 1, 10, 11, 100, 101, 110, 111, 1000,
等等),因为0可以通过开关的的关闭,而1开可以用开关的开启来分别表示。在一些其他的文化环境中,例如:古巴比伦人(ancient Babylonians),他们使用60进制,而与此同时,古罗马(ancient Romans)使用的更为专门的记法(I, II, III, IV, V, VI, VII, VIII, IX,
X),我想在这最后的那个例子告诉你,记号的表达方法问题(&notational issues)至关重要的。若是你对此不以为然(you don’t believe this),就请你来开发创造出(figuring out&)出一种新的方法系统
使用古罗马记号
长数除法/ 大数(long-division),结果呢,你就会发现,这会是一件令人丧气的事情(frustrating task)。据说,在罗马他们有一群专门的人(professionals&)(类似与现代的会计师)精通于此。(specialised in this)
好了,这里还有另外一种表达数字的方法(another way of writing
numerals),这是一种常被用在数学中的处理逻辑。这里要用到4个符号:0,succ,左括号(“,”以及右括号 “)”,下面的一种使了递推式的定义方法。
1、0&&&&&&
是一个数字
2、要是X是数字,那么 succ(x) 也是数字。
这也算是清楚了(As is probably clear),succ&
可以被读成
successor&,后继。也就是
succ(X) 表示 由X代表的那个数字 加上1 后的那个数字。所以,这是一个非常简单的
数字表示方法。它
清楚明白的说明白了:0是一个数字,并且其他的数字可以同在其前面进行叠加succ 来表示(其实,他用在数学逻辑的原因就是因为它的简单性,尽管这种方式,对在居家记账方面不是那么的友好,但,他在证明问题方面,确实一种非常不错的记号方法(notation&))。
现在,到了这一步,已经是非常清楚了,我们可以把这个定义转换成一个prolog 的程序。下面的这个知识库完成了这样的一件事:
numeral(0).
numeral(succ(X)) :- numeral(X).
所以要是我们pose 出这样的一个query:
numeral(succ(succ(succ(0)))).
我们得到答复是 yes:
不过,我们还可以做一些更为有趣的事情。想一下,要是我们pose 下面这样的一个query 会发生什么?
numeral(X).
这是在说:好了,写几个数字给我看看吧?(Ok, show me some numerals)
,然后我们就与prolog
之间有了下面的对话。
X = succ(0)
succ(succ(0)) ;
succ(succ(succ(0))) ;
succ(succ(succ(succ(0)))) ;
succ(succ(succ(succ(succ(0))))) ;
succ(succ(succ(succ(succ(succ(0)))))) ;
succ(succ(succ(succ(succ(succ(succ(0))))))) ;
succ(succ(succ(succ(succ(succ(succ(succ(0))))))))
是的,这时的prolog 是在数数呀。不过,更为重要的是,它到底是怎么做的?(&what’s really important is&how&it’s doing
this),相当的简单,他正在一递归的形式对规则定义进行后退式追溯(backtracking&),然后在使用
unification 归并的方式 构建出
数字来。这是一个很有启发性的例子(an instructive
example),对于来说,理解他非常的重要,最好的方式就是坐下来,把跟踪打开(&with&trace&turned
on),然后进行尝试(The best way to do so is to sit down and try it
构建与绑定,递归,归并,以及 证据检索(Building and binding. Recursion, unification, and
proof search)。这样的思想正是prolog 编程的核心(These are ideas that lie at the heart of Prolog
programming),不论什么时候,当我们必须开始建立或是分析一个有着递归式结构的对象色的时候(就像这些数字),这些思想相互作用(interplay)会让prolog 称为一种十分强大的工具。例如,在下一章,我们将会引入list,
它是一种非常及其强大的递归式数据结构。并且我么还是看到,prolog 正是一种可以出来自然列表数据的语言(&natural list processing
language),有很多的应用(主要是语言计算(computational
linguistics)都是大量的使用递归式的结构对象。就像:树结构(feature structures),属性特征结构(feature structures)。所以,在这些应用中,prolog
证明自己的作用,这个也是毫不奇怪的(So it’s not particularly surprising that Prolog
has proved useful in such applications.)
Example 4: Addition 例子4 加法
作为最后的一个例子,让我们来一起看一下,能够运用前面一节介绍的数字表示法(representation of numerals&)来做一些简单的算术计算。我们就试着给出一个
加法的定义把。也就是,我们要定义一个谓词predicate&add/3&,它会在把你给出前两个参数加总的结果返回,并把它放置到第三个参数之中。例如:
add(succ(succ(0)),succ(succ(0)),
succ(succ(succ(succ(0))))).
add(succ(succ(0)),succ(0),Y).
succ(succ(succ(0)))
这里有两件事情需要我们引起重视,
无论何时,只要第一个参数是0,第三个参数就得与地二个参数保持一样。
?- add(0,succ(succ(0)),Y).
Y = succ(succ(0))
?- add(0,0,Y).
这就是我们要用在
基础子句中的情况(This is the case that we want to use for the base
假如,我们要加总
这样的两个数值:X也Y(例如:succ(succ(succ(0)))&和
succ(succ(0))&),这里的X不为0,要是有这样的一个数,不妨叫它X1,X1代表的数字比X少一个
succ 函数子functor(在这个例子中,就是succ(succ(0))&),
要是我们还能够知道这样的一个结果,也不妨就把它叫做Z吧,Z是X1与Y进行加总的结果。(我们这样去叫它:succ(succ(succ(succ(0)))))。那么,计算出X与Y相加的结果就是非常的简单了。我们只要在Z的前面再加上一个
succ&-functor
函数子就可以了。而这就是我们要表达的递归子式。
这就是我们刚才所描述的谓词的定义:
add(0,Y,Y).
add(succ(X),Y,succ(Z)) :-
&&&&&&& add(X,Y,Z).
那么,发生了什么呢?要是我们给出一个这样的谓词定义,然后去问:
?- add(succ(succ(succ(0))), succ(succ(0)), R).
现在,我们再次一起来一步一步的进入到Prolog的执行过程里面
来探究这个请求(Let’s go step by step through the way Prolog
processes this query),跟踪以及检索树(search tree)也画在了下面:
第一个参数不是0,也就是说,只有第二个字句式才可使用,它会产生(leads to&)一个递归式的调用:&add/3&,原始请求(original query)的第一个参数的最外层的
succ&functor&被剥离下来,结果,它成为了这个递归请求的第一个参数。而第二个参数则是原封不动的的被传递到递归请求之中,递归请求里面的第三个参数,他是一个内部变量
在下面的使用 _G648来表示&,注意到
还没被初始化,不过它会与R
变量(R变量
就是我们在原始的请求(original query)用在参数三的位置的那个变量)共享结果值的。原因在于,在请求开始使用第二个字句的规则头进行归并的时候,R就会被succ(_G648)&
给实例化的。不过,那也就是说,R不再是一个完全没有被实例化话的变量了。现在他还是一个复合项(complex term),有一个还没被实例化的参数。
接下来的两步在本质上都是一样的,他们的每一步都是会令第一个参数的succ
变薄一层,在下面的跟踪(trace)和
检索树(&search tree)中
表达的都很清楚了,与此同时,&succ&functor(函数子),也都会在每一步里被附加到R上面,但是,却始终保留在它(R)的最里面的那个变量(&innermost variable)不被实例化。在第一次的递归调用之后
是succ(_G648)&,
在第二次的递归调用之后,_G648
又被实例化成
succ(_G650)&,所以,R就成了
succ(succ(_G650)&,当第三次的递归调用完成后,_G650&
又被实例化为succ(_G652)&,并且
R就变成了succ(succ(succ(_G652)))&,检索树
这个一步步进行实例化的过程。
在第一个参数的所有的succ&functors
函数子都被剥离掉之后,就进入了我们可使用第一项柜子来进行的阶段(stage&),
即:第三个参数需要与第二个参数相等。所以
最终,那个在复合项R中的孔洞(没有被实例化的变量)就可以被实例化了。并且我们也重头到尾的走完了一遍(&we are through)。
这就是我们
这次请求的完整
跟踪记录(Here’s the complete trace of our
query:):
add(succ(succ(succ(0))), succ(succ(0)), R)
add(succ(succ(0)), succ(succ(0)), _G648)
add(succ(0), succ(succ(0)), _G650)
add(0, succ(succ(0)), _G652)
add(0, succ(succ(0)), succ(succ(0)))
add(succ(0), succ(succ(0)), succ(succ(succ(0))))
add(succ(succ(0)), succ(succ(0)),
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
succ(succ(succ(succ(0)))))
add(succ(succ(succ(0))), succ(succ(0)),
&&&&&&&&&&&&&&&&&&&&&&&&&
succ(succ(succ(succ(succ(0))))))
下面就是 这次的检索树:
<img WIDTH="496" HEIGHT="492" ALT="*Pic not found*" V:SHAPES="图片_x0020_3"
TITLE="prolog&第二篇" />
3.2&Rule Ordering, Goal Ordering, and
Termination 规则额排序,目标的次序,还有 终止。
Prolog 的最初想法(它是相当的成功),就是创建一种逻辑辑编程语言(logic programming language),在 逻辑编程
(logic programming)所暗含了这样的一个
简单且诱人(seductive)图景(vision):
programmer 程序员 的工作就是 把 问题描述出来,程序员 需要把 用逻辑语言的形式 把问题 的
直白说明(declarative
specification)也就是知识库,即我们所感兴趣的情形(which describes the situation of
interest)。给表达出来(write down),而 程序员是不需要告诉计算机
究竟该如何去做的。要想获取信息,程序员就 只要简单的去问答案就可以了。一个逻辑编程系统 应该有能力(is up to)找出方法并求解出(figure out&)答案。
好吧,就是这个思想而言。这个方向上 prolog 迈出一些重要的步骤,但是,prolog 没有,还没有(repeat not) 成为一种 真正意义上的 逻辑编程语言。要是你只是去思考一个prolog 的直白语义(declarative meaning of a Prolog program),
你就会在处在一种十分艰难的情景(you are in for a very tough
time),这是因为,就像我们在前面章节中学到的那样,prolog 对请求的求解方式是非常特殊的:它要从上至下的检索扫描知识库(searches the knowledge base from top to
bottom),从左到右的分析子句型(clauses from left to right),并且使用后退(backtracking&),从那些糟糕的选择中恢复回来(uses backtracking to recover from bad
choices.&)这些过程特征(procedural aspects)会对你提起的询问(make a query)在实际的执行过程产生十分严重的影响。我们已经见到过大量的在过程/程序式 与 直白语义(declarative meaning)之间的不一致。(还记得:规则,p:-&&p吗?&),就像我们将要看到的那样:定义些描述同样的情景问题的(situations)知识库不很麻烦,至少逻辑在读起来相同,(it is easy to define knowledge bases which (read
logically) describe the same situations),但他们在运行中的行为却是十分不同,现在:我们就开始思考这些事情:
回想一线,先前的 后代程序,(就叫它:descend1.pl 吧):
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
&&&&&&&&&&&&&&&& descend(Z,Y).
我们对它做一个小的改动,并把它叫做:&descend2.pl&
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y) :- child(X,Z),
&&&&&&&&&&&&&&&& descend(Z,Y).
descend(X,Y) :- child(X,Y).
我们所做的感动只是调整了一下顺序,要是我们以一种完全逻辑式的方法来读这个程序,什么都没变,不过,这对程序的执行过程会产生改变吗?(does the change give rise to procedural
differences?),是的,不过还好(but nothing
significant),比如:在你执行(work through)这些例子的时候,你会发现,descend1.pl&的查出的第一组答案是:
Y = bridget
此外,descend2.pl&
查出的第一组答案:
不过,两个程序还是可以产生一样的结果的(因为你可以亲自去检查),他们只是在次序上有些不同,并且这一点是普遍的(&And this is a general
point),不严格的说(Roughly speaking),我会在后面加入一个caveat&(附加说明),在prolog 程序中,改变了规则顺序,不会改变程序的执行行为的(up to the order in which solutions are
found,至多是结果的排序有些不同)。
我们再来更进一步,我们把 descend2.pl&
在做一点小改动,我们把它叫做
descend3.pl。
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y) :- descend(Z,Y),
&&&&&&&&&&&&&&&& child(X,Z).
descend(X,Y) :- child(X,Y).
注意到,差别,这里我们懂了一下规则的目标顺序,而不是规则的顺序,我们再来一遍,要是你依据逻辑定义来读一遍这个程序,什么也没变。他寿命,这个同一个东西的两个版本,不过,这次,程序的运行行为(program’s behaviour&)就
彻底改变了(changed dramatically),例如:你要是,pose 请求:
?- descend(anne,emily).
你会得到一个错误信息回复,(“out of local stack”,&
本地栈溢出,后者是类似的信息),Prolog 一直在循环。好吧,为了去满足请求:descend(anne,emily)&,prolog 使用第一个规则,也就是说,它接下来的目标就是去满足请求:
?- descend(W1,emily).
这是一个新的变量W1,不过要是去满足合格 新目标,prolog 就需要再一次使用第一个规则,也就是说,它的下一个目标,将会是:
?- descend(W2,emily).
这就是一个新的变量W2, 结果,这样的交替出现了下一个目标是:&descend(W3,emily)&,然后再是:descend(W4,emily)&,等等。也就是(一眼看上的去的平淡无奇(innocuous))对于目标之间顺序的变动,会造成程序过程上的灾难。(procedural disaster),使用一个术语来描述一下:这里我们就是那个经典的问题:左递归。即:一条规则,它的最左边的项目(去掉变量部分的余下部分,modulo the choice of variables) 与 规则的头部是
是相同的(identical&)。就像我们的例子中给出的那样,这样的规则,很容易给出一个不会终结运算行为。目标的次序(Goal order),特别是左递归,在出现无法终结的时候,它就是所有罪恶的根源。(&in particular left recursion, is the root of all
evil when it comes to non-termination.)
然而,就像我们先前说过的一样,我们需要在这里对于规则排序给予一个小的告诫式说明。先前我们说过规则的次序仅仅会改变结果(solution)里面的分组次序。不过,要是我们正在运行的程序是进入一种无法终结过程中(&non-terminating&),这个结论就不该这样了。想看吧,在我们的后代程序中,思考一下这四个变量(还有最后的那个),就叫它
descend4.pl& 吧:
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend(X,Y) :- child(X,Y).
descend(X,Y) :- descend(Z,Y),
&&&&&&&&&&&&&&&&
child(X,Z).
这个程序是 把&descend3.pl&
中的规则次序
进行了反转(ordering reversed),现在,再一次的,发这个程序
就像其他的变形式(the other variants)一样
同样的直白语义(declarative meaning),不过,依据它的自己的内部关系
执行过程上的差别(but it is also procedurally different from its
relatives.),首先,也是最明显的,它是和
descend1.pl&和
descend2.pl&
在过程的执行上是
非常不同。特别是,在它里面包含有左递归式的规则,这个新的程序会因一些输入
而无法终结计算。比如,(就像:descend3.pl&),这个新式程序
在我们pose 下面的query 就不会终结:
descend(anne,emily).
不过descend4.pl 还不能算是 与
descend3.pl&
过程式等价
(identical),它那逆转了次序规则
还做了一些改变。比如:要是我们pose 下面的请求,descend3.pl 无法终结:
descend(anne,bridget).
然而,&此种情况
descend4.pl&
确能够终结掉。正是由于
这种规则的倒置
让它能够先接触到了
非递归式的规则,并且终止(halt)了下来。所以,当我们,进入了到一种
无法终结的过程(programs)之中,规则次序的改变是可以产生(&lead t)其他的答案的被找到。虽然如此,是goal
目标的次序
而不是规则的次序
让我们的执行过程
那样的千差万别(what is truly procedurally
significant.&)。要想保证我们的程序可以终结,我们就需要对
规则的各个goal
之间的次序,十分的留意。对规则排序的小改动(Tinkering&)
是无助于,抓住程序终结问题的根本。最好的情况,它会获得(yield&)一起别的结(solutions)。
总结一下,我们这里的四个后代-知识库变体
他们都是描述了完全一样的情况(直白语义 same situations),但是
它们的行为上
确实十分的不同,descend1.pl&
和 descend2.pl&
(他们的差别只是规则的排序)的不同是非常小的:他们产生的答案也是hi相同的,只是顺序不一样而已。但是
descend3.pl&
和 descend4.pl
则与他们的兄弟有着非常不同的
过程语义。这是因为他们的
规则里面的 goal
目标的次序不一样。&特别是,这两个变体里面,都使用了左递归。并且,他们两儿还都会
无法终结的行为。在 descend3.pl&
和 descend4.pl
之间改变规则的次序。也只是
descend4.pl
descend3.pl
不会终结的情况
我们讨论的结果(ramifications&),也就是
程序的执行过程里实用性(practicalities of producing working&),到底是什么呢?它可能最好像下面的这样去说:我们往往运用一个全局思想(overall idea,)
大画面(he big picture),去思考我们的直白语义(&thinking declaratively)
编写程序。也就是,描述问题要以精准为基础。(by thinking in terms of describing the problem
accurately),这是一种对于求解问题而言的相当绝妙的方法。并且它也是最能够保持着逻辑编程的
精髓。不过,一旦你已经完成这一部分工作,你就要去思考一下prolog
会如何使用你刚写的知识库来进行工作。特别是要注意,他要能够停下来,终止的。你必须要去检查一下你写的那些规则的goal
的次序是否有意义(sensible),基本准则就是(The basic rule of thumb&):绝不要这样去写,规则体中的最左边的goal
会有某些地方(除掉参数变量之后)
出现在规则头里面。还有,要把这种规则朝着最右边的的尾部方向放置的越远越好,(The basic rule of thumb is never to write as the
leftmost goal of the body something that is identical (modulo
variable names) with the goal given in the head. Rather, place such
goals (which trigger recursive calls) as far as possible towards
the right of the tail.&)也就是,把他们放在那些可以检测出各种(非递归)的种植条件的
之后。这样做就给出prolog
一次去运动(chance&),去拼搏(fighting&)机会,用它自己的方式
最终的答案。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 usb外置声卡 的文章

 

随机推荐