4.4.2 查询系统如何工作

4.4.2 查询系统如何工作
在4.4.4部分中我们将把查询解释器的实现程序表示为一系列的程序的组合而成的集合。在这一部分中,
我们给出一个概述,来解释系统的通用的结构,它是独立于低层的实现细节的。在描述了解释器的实现
之后,我们将处于一个位置,也就是理解了查询语言的逻辑操作与数学的逻辑操作的不同之处,其中的
一些局限之处,一些很微妙的方式。

为了把查询与数据库中的事实与规则进行匹配,查询的解释器必须执行一些类型的搜索,
应该是很明显的事了。完成这个任务的一种方式是实现查询系统为一个非确定性的程序,
使用4.3部分中的amb解释器(见练习4.78)。另一个可行的方法是在流的帮助下,
管理好搜索。我们的实现采用第二种方法。

组织起查询系统,是围绕着两种中心的操作的,它叫做模式匹配和统一。我们首先描述
模式匹配,然后解释这种操作如何结合信息的组织,用流的帧,让我们能够实现简单的
和复合的查询。我们接下来讨论统一,模式匹配的泛化需要实现规则。最后,我们显示
整个查询解释器如何结合一个分类的程序,以一种与eval分类表达式相似的方式,
而这种分类是在4.1部分中描述的解释器中实现的。

*模式匹配
一个模式匹配器是一个程序,它测试一些数据是否符合一个特定的模式。例如,
数据列表((a b)  c  (a  b)) 匹配模式 (?x   c  ?x)并且模式变量?x 绑定为(a  b).
相同的数据列表匹配模式 (?x  ?y  ?z)并且模式变量 ?x 和?z都绑定到(a  b),?y
绑定到c。它也能匹配模式((?x   ?y)  c  (?x   ?y))并且?x 绑定到 a 和 ?y 绑定到 b。
然而,它不能匹配模式(?x  a  ?y),因为模式要求一个列表的第二个元素是符号a. 

查询系统使用的模式匹配器,它有三个参数,是一个模式,一个数据,一个帧它指定了
多个模式变量的绑定情况。它以与帧中的绑定一致的方式来检查数据是否匹配模式。
如果匹配,它返回给定的帧,而帧的绑定是由匹配来确定的。否则它显示匹配失败。

例如,使用模式(?x ?y ?x)来匹配(a  b  a),还有一个空的帧,将返回一个帧,它指定了
?x 绑定了 a和  ?y 绑定了b。试着匹配,以相同的模式和相同的数据,还有一个帧,它指定了
?y 绑定了a,将匹配失败。试着匹配,以相同的模式和相同的数据,还有一个帧,它指定了
?y 绑定了b,而?x没有绑定,将返回给定的帧,帧中新增了一个绑定,是x 绑定了 a。

为了处理没有包括规则的简单的查询,模式匹配器就是所需要的所有的机制了。
例如,处理如下的查询

(job  ?x  (computer programmer))

我们能扫描数据库中的所有的记录,查找 并且匹配模式,并且对应一个初始化为空的帧。
对于我们发现的任何一个匹配,我们使用被匹配返回的帧,匹配在实例化模式时以a代替?x.

* 帧的流
带有帧的模式测试被组织为使用流的方式。给定的一个单独的帧,
匹配过程的运行是通过对数据库一条条记录的检查。对于任何一条数据库的记录,
匹配器生成一个特定的符号来显示匹配已经失败或者是对帧进行扩展。所有的数据库的记录
的结果被收集到一个流中,通过一个过滤器,剔除失败的。结果是对给定的帧进行扩展的所有
的帧的流和对数据库中的一些记录的匹配。

在我们的系统中,一个查询以一个帧的输入流,并且针对流中的每个帧,执行如上的匹配操作。
正如在图4.4中,显示的那样。也就是说,在输入流中的每个帧,查询生成一个新的流,通过匹配
数据库中的记录来扩展那个帧。所有的这些流然后组合成一个巨大的流,它包括了输入流中所有的帧的
所有的可能的扩展。这个流是查询的输出。

扫描二维码关注公众号,回复: 3662137 查看本文章

帧的输入流        ————————  帧的输出流 过滤并扩展
——————>|查询(job  ?x  ?y)    |——————>
                        |————————|
                                     ^
                                      |
                                      |
                                   数据库中记录的流

图4.4   一个查询处理帧的图

为了回答一个简单的查询,我们使用带有输入流的查询,流由一个单独的空帧组成。输出的结果流
包括了对空帧的所有的扩展(也就是对于我们的查询的所有的结果)这个帧的流然后被用来生成原来的
查询模式的一个复制的流,并且模式的变量被帧中的值实例化了,这是最终被打印出来的流。

* 复合的查询
当我们处理复合的查询时,帧的流的实现是真正的优雅是很明显的。复合的查询的处理
利用了我们的匹配器的能力,它要求一个匹配与特定的帧保持一致。例如,要处理两个查询的and,
例如

(and  (can-do-job  ?x  (computer  programmer  trainee)) 
         (job  ?person ?x))

(正式的,“找到所有的做计算机程序员培训的工作的人”),我们首先找到匹配如下的模式的
所有的记录
 (can-do-job  ?x  (computer  programmer  trainee)

这产生了帧的流,它的任何一个元素都包括了?x的绑定。然后对于在流中任何一个帧
我们找所有的匹配(job ?person ?x)的记录。以与给定的?x的绑定一致的方式进行。
任何一次匹配将产生一个包括了?person 和?x的绑定的帧。这两个查询的与,能被视
作这两个单独的查询的序列的组合,如图4.5所示。帧通过第一个查询的过滤器被过滤,
再进而被第二个查询扩展。

帧的输入流        ————————  帧的输出流
                        |   (and  A   B)    |
——————  |—>|  A  |—>| B | - |-————————>
                        |——^——— ^— |
                                      |
                                      |
                                      |
                                   数据库

图4.5   通过操作帧的流,生成两个查询的与操作组合。

图4.6显示了计算两个查询的或操作的相似的方法,作为两个单独的查询的并发的组合。
输入的流被单独的扩展。两个结果流然后被合并来生成最终的输出流。

帧的输入流        ————————  帧的输出流
                        |   (or  A   B)        |
                        |                               |
                        |  | >|  A  |------\/     |
——————  |-|      ^     |merge|   |———>
                        |  |—>| B | ---—^    |
                        |         |  ^                |
                        |————|——— —|
                                       |
                                       |
                                   数据库
图4.6  通过并发的和合并结果,操作帧的流,生成两个查询的或操作组合。

甚至从这种高层次的描述,复合的查询的处理能够是很低效的,这也是很明显的事。
例如,因为一个查询可能为一个输入流生成多个输出流,并且任何在与操作中的一个查询要从
之前的查询中得到输入流,一个与查询,最坏的情况下,不得不执行查询个数的指数级的匹配个数。
(见练习4.76)尽管系统处理仅简单的查询是可行的,但是处理复杂的查询是相当困难的。

从帧的流的视角上看,一些查询的非操作的行为像一个过滤器,它移除了能被满足的查询。例如,
给出如下的模式

(not  (job  ?x  (computer  programmer)))

我们试着为输入流的任何一个帧,生成扩展的帧,来满足(job  ?x  (computer  programmer))。
然后我们从输入流中移除所有的帧。结果是流仅包括了那些帧,帧中的绑定不满足(job  ?x  (computer  programmer))。 例如,在处理查询

(and (supervisor ?x  ?y)
  (not  (job  ?x  (computer  programmer))))

第一个子句生成了帧,绑定了?x 和 ?y。非子句过滤了这些内容,移除了所有的满足?x是
一个程序员的条件限制。

lisp-value的标识符的实现作为在帧的流上的一个相似的过滤器。我们使用流上的每个帧
来实例化模式上的任何变量,然后应用Lisp的判断式。我们从输入流上移除
所有的让判断式失败的帧。

* 统一
在查询语言中,为了处理规则,我们必须能够找到规则的结论部分能够匹配
一个给定的查询模式的规则。规则的结论像一个断言,除了它们能包括变量,
所以我们需要一个模式匹配的泛化,叫做统一,在这种情况下,模式和数据
可能都包括变量。

一个统一器以两个模式为参数,任何一个模式都包括常数和变量,确定是否可能
赋值给变量,并且让两个模式相等。如果是这样的,返回一个包括了这些绑定的帧。
例如,统一化 (?x   a   ?y) 和 (?y  ?z   a) 将指定一个帧,它的变量?x,?y,?z都绑定到a.
另一个方面,统一化(?x  ?y  a)  和(?x  b   ?y)将失败。因为没有一个?y的值能让这两个
模式相等。(为了让模式的第二个元素相等,?y必须等于b,然而,为了让第三个元素相等,
?y必须等于a.)在查询系统中使用统一器,像模式匹配器一样,以一个帧为输入,执行
统一操作,与这个帧的内容要保持一致。

统一化的算法是查询系统 中最有技术难度的部分。在复杂的模式中,执行统一操作,
可能似乎需要推导。为了统一化(?x  ?x)  和 ((a  ?y   c)   (a   b  ?z)) , 例如算法必须推理得到
?x 应该是(a b c),?y 应该是b, ?z 应该是 c. 我们可能认为这个过程像是在模式的组件之间
解一系列的方程。总之,这些同时性的方程,可能要求有大量的操作来求解。例如,
统一化(?x  ?x)  和 ((a  ?y   c)   (a   b  ?z)) 可能被认为是如指定同时性的方程

?x  =  (a  ?y   c)
?x  =  (a  b    ?z)

这些方程应用为

 (a  ?y   c)=  (a  b    ?z)

进一步推导为

a=a, ?y=b,c=?z,

并且因此得到

?x= (a b c)

在一个成功的模式匹配,所有的模式变量都被绑定,它们被绑定的值仅包括
常数。我们已经看到过的所有的例子中都是这样的。总之,然而,一个成功的
统一可能没有完全地确定了变量的值,一些变量可能保持着未绑定的状态,其它的
变量可能被绑定的值中含有变量。

考虑(?x   a) 和 ((b  ?y)   ?z)的统一。我们能够推导得到?x=(b  ?y)和
a=?z,但是我们不能进一步地得到?x,?y的值了。统一没有失败,因为
通过给?x,?y赋值, 肯定可能有值让这两个模式相等。因为这个匹配没有限制
?y的值,所以?y的未绑定的状态进入了结果帧。这个匹配的操作,限制了?x的值
。无论?y的值是什么,?x的值必须是 (b  ?y). ?x到模式(b  ?y)的绑定因此进入了
结果帧。如果?y的值在稍后被确定了,并且加入到帧中,(通过一个模式匹配或者
是统一,被要求与这个帧保持一致)对?x的绑定将引用这个?y的值。

* 应用规则
统一是查询系统从规则做引用的组件的核心。为了看看这是如何完成的,
考虑处理一个包括了应用一个规则的查询,例如

(lives-near  ?x  (Hacker  Alyssa  P))

为了处理这个查询,我们首先使用如上描述的普通的模式匹配程序来看看
在数据库中是否有匹配这个模式的记录。(在这个例子中,没有这样的记录,因为
我们的数据库中,没有包括谁离谁近的直接记录)。下一步是试图统一查询模式
与每个规则的结论。我们发现模式统一规则的结论

(rule  (lives-near   ?person1  ?person2)  
         (and  (address   ?person1  (?town .   ?rest1))
                  (address   ?person2  (?town .   ?rest2))
                  (not (same  ?person1 ?person2))))

在一个帧中的结果是指定了 ?person2绑定为(Hacker Alyssa P),?x绑定了?person1.
现在,相对于这个帧,我们解释了由规则的内容体给出复合的查询。成功的匹配将扩展
这个帧,通过提供一个对?person1的绑定,因此,?x的值,我们能用来实例化最初
的查询模式。

总之,查询解释器当尝试在一个帧(在帧中指定一些模式变量的绑定)中建立一个
查询模式时,使用如下的方法,来应用一个规则:

  . 统一查询与规则的结论,如果成功,得到一个原始的帧的扩展。
  . 相对于扩展的帧,解释由规则的内容体形成的查询
注意的是这与在eval/apply的Lisp解释器中应用一个程序的方法是多么的相似啊。
  . 绑一定要程序的参数到它的实际参数,来形成一个帧,以扩展原来的程序环境。
  .相对于扩展的环境,解释由程序体形成的表达式。

这两个解释器之间的相似性并不让人惊奇。正如程序定义是在lisp中的抽象的方法,
在查询语言中,规则定义是抽象的方法。在任何一个例子中,我们解开抽象,都是通过
创建合适的绑定,并且解释规则或者是它们对应的程序体。

* 简单查询
在规则缺席的情况下,在这部分中,我们先看到了如何解释简单的查询。
现在我们要看一看如何应用规则,我们能够描述在使用规则和断言的情况下,
如何解释简单的查询。

给出查询模式和一个帧的流,我们生成了输入流的帧,两个流:

   .  通过在数据库中匹配所有的断言(使用模式匹配器)得到一个扩展的帧的流。
   .  通过应用所有的可能的规则(使用统一器)得到一个扩展的帧的流。

合并如上的两个流生成一个流,它由给出的模式能被满足的,并且与
原帧一致的所有的记录组成。这些流(在输入流的任何一帧)现在组合成一个大的流,
因此它包括了原始的输入流的帧能被扩展的所有的记录,为了生成对给定的模式的匹配。

* 查询解释器与驱动循环
尽管在匹配操作的复杂性,系统被组织得很像与任何其它语言一样的一个解释器。程序协调
匹配操作的叫做qeval,并且它扮演的角色与lisp的eval的程序很相似。qeval以一个查询和
一个帧的流为参数。它的输出是一个帧的流,对应于对查询模式的成功匹配,并且它扩展了
输入流中的一些帧,如图4.4中的显示。像eval,qeval分类了表达式(查询)的不同的类型,
并为它们每个类型分发了一个适合的程序。对于每个标识符(and,or,not ,lisp-value)和简单
的查询都有一个程序。

驱动循环与在这一章中的其它的解释器的驱动循环程序是很相似的,从终端中读取查询。
对于每一个查询,它调用qeval,带有一个查询,一个流和一个空的帧。这将生成所有的
可能的匹配的流。在结果的流中每一帧,它实例化原始的查询,使用在帧中找到的变量的值。
这个实例化的查询的流被打印出来。

驱动也检查特殊的命令assert!,它显示了输入的内容不是一个查询,而是一个断言或者是
被加入到数据库中的规则。例如,

(assert! (job  (Bitdiddle  Ben)  (computer wizard)))
(assert! (rule  (wheel  ?person)
        (and  (supervisor  ?middle-manager  ?person) 
                 (supervisor  ?x   ?middle-manager))))

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/82994286