转载自:https://mp.weixin.qq.com/s/SeMjK-_XR69jSQEQ-iRHMg

本章有两个主要目标:

1. 讨论Prolog中的合一,并解释Prolog合一与标准合一有何不同。在此过程中,我们将introduce/ 2,Prolog合一的内置谓词,并与check / 2合一,即标准合一的内置谓词。

2. 解释Prolog在尝试使用惯用方式从旧信息中推断出新信息时使用的搜索策略。

合一

在上一章中使用知识库KB4时,我们简要提到了合一的思想。例如,我们说过Prolog将woman(X)与woman(mia)进行合一,从而将变量X实例化为mia。现在是时候仔细研究合一了,因为它是Prolog中最基本的思想之一。

回想一下,有三种项:

1.常数。这些可以是原子(如vincent)或数字(如24)。

2.变量。 (如X,Z3和List。)

3.复合项。它们具有以下形式:

functor(term_1,…,term_n).

我们将努力确定Prolog何时将两个项合一的绑定。我们的出发点将是以下绑定过程。它给出了基本的直觉,但在细节上略有改动:

两个项是合一的,还是它们包含可以用项合一实例化的变量,使得结果的项相等。

例如,这意味着项mia和mia合一,因为它们是同一原子。类似地,项42和42合一(因为它们是相同的数字),项X和X合一(因为它们是相同的变量),而项woman(mia)和woman(mia)合一,因为它们是相同的复合项。但是,项woman(mia)和woman(vincent)并不合一,因为它们不相同(并且它们都不包含可以实例化以使其相同的变量)。

现在,项mia和X呢?它们并不相同,但是变量X可以实例化为mia,这使它们相等。因此,根据我们的工作定义的第二部分,mia和X是合一的。类似地,项woman(X)和woman(mia)是合一的,因为可以通过将X实例化为mia来使它们相等。loves(vincent,X)和loves(X,mia)怎么样?不能。不可能找到使两个项相等的X的实例。你明白为什么吗?将X实例化为vincent会使我们得到loves(vincent,vincent)和loves(vincent,mia)这两个项,这显然是不相等的。但是,将X实例化为mia会产生loves(vincent,mia)和loves(mia,mia)项,两者也不相等。

通常,我们不仅对两个项合一这一事实感兴趣,而且我们还想知道如何实例化变量才能使其相等。而Prolog向我们提供了这些信息。当Prolog合一两个项时,它将执行所有必要的实例化,因此之后这些项实际上是相等的。此功能以及允许我们构建复杂项(即递归结构的项)的事实,使合一成为一种强大的编程机制。

基本直觉现在应该清楚了。这是使它们精确的定义。它不仅告诉我们Prolog将合一哪些项,而且还告诉我们变量将如何实现这一目标。

1.如果term1和term2是常数,则term1和term2当且仅当它们是相同的原子或相同的数字时才合一。

2.如果term1是变量,而term2是任何类型的项,则term1和term2合一,并且term1实例化为term2。同样,如果term2是变量,而term1是任何类型的term,则term1和term2合一,并且term2实例化为term1。 (因此,如果它们都是变量,则它们都将彼此实例化,并且我们说它们共享值。)

3.如果term1和term2是复合项,则只有在以下情况下它们才可以合一:

(a)它们具有相同的函子和元数,并且

(b)所有其相应的参数合一,并且

(c)变量实例是兼容的。 (例如,在合一一对参数时无法将变量X实例化为mia,而在合一另一对参数时则无法将变量X实例化为vincent。)

4.当且仅当这两个项是根据前三个条款的合一来合一的。

让我们看看这个定义的形式。第一个条款告诉我们两个常量何时合一。第二个条款告诉我们何时两个项(其中一个是变量)合一(这些项将始终合一;变量与任何事物合一)。同样重要的是,该条款还告诉我们必须执行哪些实例化才能使两个项相同。最后,第三个条款告诉我们两个复合项何时合一。注意此定义的结构。它的前三个条款完美地反映了项的(递归)结构。

第四条很重要:它说前三个条款告诉我们所有关于两个项合一的知识。如果使用1-3条无法显示两个项可以合一,那么它们就不能合一。例如,batman不与daughter(ink)合一。那么为什么呢?第一个项是一个常数,第二个项是一个复合项。但是前三条中没有一个告诉我们如何合一两个这样的项,因此(按第4条)它们不是合一的。

例子

为了确保我们完全理解此定义,让我们来看几个示例。在这些示例中,我们将使用一个重要的内置谓词= / 2谓词(回想一下,在末尾写/ 2表示该谓词带有两个参数)。

= / 2谓词测试其两个参数是否合一。例如,如果我们构成查询

?- =(mia,mia).

Prolog将回答true,如果我们提出查询

?- =(mia,vincent).

Prolog会回应 false.

但是我们通常不会以这种方式提出这些查询。面对现实吧,=(mia,mia)表示法是很不自然的。如果我们可以使用中缀表示法(即,可以将= / 2函子放在其参数之间)并编写类似以下内容,那就更好了:

?-mia = mia.

实际上,Prolog允许我们执行此操作,因此在以下示例中,我们将使用中缀表示法。

让我们回到第一个示例:

?- =(mia,mia).

true.

为什么Prolog会说true.?这似乎是一个愚蠢的问题:毫无疑问,x项的合一!是的,但是从上面给出的定义来看,这是怎么做的?重要的是要学会系统地思考合一(这对Prolog来说是根本的),而系统地思考意味着将示例与上述给出的合一定义联系起来。因此,让我们仔细考虑一下这个示例。

该定义包含三个条款。现在,条款2是针对一个参数是变量的情况,条款3是针对两个参数都是复合项的情况,因此在这里没有用。但是,条款1与我们的示例有关。这告诉我们两个常数在且仅当它们是完全相同的对象时才合一。由于mia和mia是同一原子,因此合一成功。

类似的参数解释了以下响应:

?- 2=2.

true.

?- mia = vincent.

false.

再次,条款1在这里是相关的(毕竟,2,mia和vincent都是常量)。由于2与2相同,并且mia与vincent是不同的原子,因此Prolog对第一个查询回答“true”,对第二个查询回答“false”。

但是,条款1确实给我们带来了一个小惊喜。考虑以下查询:

?- 'mia' = mia.

true.

这里发生了什么?为什么这两个项合一?好吧,就Prolog而言,“ mia”和mia是同一个原子。实际上,对于Prolog,任何形式为“符号”的原子都被视为与形式符号的原子相同的实体。在某些程序中,这可能是有用的功能,所以请不要忘记它。

另一方面,要查询

?- '2'=2.

false.

Prolog将回答false。而且,如果您考虑第1章中给出的定义,您将发现这必须是这样的工作方式。毕竟2是一个数字,而 ‘2’是一个原子。它们根本是不能相同的。

让我们尝试一个带有变量的示例:

?- mia = X.

X = mia.

再次,这是一个简单的示例:显然,变量X可以与常量mia合一,而Prolog可以这样做,并告诉我们它已经实现了合一。很好,但是从我们的定义来看这如何?

这里的相关是条款2。这告诉我们,当至少一个参数是变量时,会发生什么。在我们的示例中,第二项是变量。定义告诉我们合一是可能的,并且还说变量被实例化为第一个参数,即mia。当然,这正是Prolog所做的。

现在举一个重要的例子:以下查询会发生什么?

?- X=Y.

好吧,根据您的Prolog执行,您可能只是返回输出

?- X=Y.

X = Y.

Prolog只是简单地同意这两个项是合一的(毕竟,变量是任何东西都可以合一,因此它们肯定是彼此合一的),并指出从现在开始,X和Y表示相同的对象,即共享值。

另一方面,您可能会得到以下输出:

?- X=_5071.

X = _5071.

?- Y=_5071.

Y=_5071.

这里发生了什么?本质上是一样的。注意_5071是变量(从第1章回忆起,以下划线字符开头的字母和数字字符串是变量)。现在来看一下合一定义的条款2。这告诉我们,当两个变量合一时,它们共享值。因此Prolog创建了一个新变量(即_5071),从现在开始X和Y都共享该变量的值。实际上,Prolog为两个原始变量创建了一个公共变量名。不用说,数字5071并没有什么神奇之处。Prolog仅需要生成一个全新的变量名,使用数字是一种便捷的方法。可能只是以及生成_5075或_6189等。

这是仅涉及原子和变量的另一个示例。您认为Prolog会如何回应?

?- X=mia, X=vincent.

Prolog将回答false。该查询涉及两个目标,X = mia和X = Vincent。分开说来,Prolog将在两者上都成功,在第一种情况下将X实例化为mia,在第二种情况下将X实例化为Vincent。这正是这里的问题:一旦Prolog实现了第一个目标,X就会实例化为mia(因此等于mia),因此它根本无法与vincent合一。因此,第二个目标失败了。实例化变量不再是一个真正的变量:它已经成为实例化的对象。

现在,我们来看一个涉及复合项的示例:

?- k(s(g),Y)=k(X,t(k)).

X = s(g),

Y = t(k).

显然,如果执行了指定的变量实例,则这两个复合项会合一。但是,如何从定义中得出结论呢?好吧,首先,必须在这里使用条款3,因为我们试图合一两个复合项。因此,我们需要做的第一件事是检查两个复合项是否具有相同的函子和元数。他们做到了。第3章还告诉我们,我们必须合一每个复合项中的相应参数。那么第一个参数s(g)和X是否合一?根据条款2,是的,我们将X实例化为s(g)。那么第二个参数Y和t(k)是否合一?再次通过条款2,是的,我们将Y实例化为t(k)。

这是另一个带有复合项的示例:

?- k(s(g),t(k)) = k(X,t(Y)).

X =  s(g),

Y = k.

应该清楚的是,如果执行这些实例化,这两个项是合一的。但是,您可以逐步解释这与定义之间的关系吗?

这是最后一个示例:

?- loves(X,X) = loves(marcellus,mia).

这些项合一吗?不,他们没有。的确,它们都是复合项,并且具有相同的函子和元数,但是条款3还要求所有对应的参数必须合一,并且变量实例化必须兼容。这里不是这种情况。合一第一个参数将使用marcellus实例化X。合一第二个参数将用mia实例化X。无论哪种方式,我们都被封锁。

发生检查

合一是一个众所周知的概念,已在计算机科学的多个分支中使用。已经对其进行了深入研究,并且已知许多合一算法。但是Prolog在执行其合一版本时不使用标准的合一算法。相反,它需要一个捷径。您需要了解此快捷方式。

考虑以下查询:

?- father(X) = X.

这些项是否合一?标准的合一算法会说:“false,他们没有”。这是为什么?好吧,选择任何项并将X实例化为您选择的项。例如,如果将X实例化为father(father(butch)),则左侧变为father(father(father(butch)),而右侧成为father(father(butch))。显然,这些并不能合一。此外,将X实例化为哪个项也没有关系。无论您选择什么,这两个项都可能无法相同,因为左侧的项将始终比右侧的项长一个符号(左侧的函子father将始终为其赋予一个附加级别) 。标准合一算法会对此进行识别(我们很快会在讨论发生状态检查时看到原因),然后停止并告诉我们“否”。

先前给出的Prolog合一的递归定义不会这样做。因为左项是变量X,所以根据条款2,它确定这些项确实是合一的,并且(根据条款2)实例化X到右侧,即father(X)。但是在这个项中有一个X,并且X已实例化为father(X),因此Prolog意识到father(X)确实是father(father(X))。但是这里也有一个X,并且X已经实例化为父亲(X),因此Prolog意识到father(father(X))确实是father(father(father(X))),依此类推。将X实例化为father(X)之后,Prolog致力于执行无休止的扩展序列。

至少,这就是理论。在实践中会发生什么?好吧,在较早的Prolog实施中,我们刚刚描述的就是所发生的。您将收到类似以下的消息:

没有足够的内存来完成查询!

以及一长串符号,例如:

X = father (father (father (father (father (father

(father (father (father (father (father (father

(father (father (father (father (father (father

(father (father (father (father (father (father

(father (father (father (father (father (father

Prolog拼命尝试使用正确的实例化项返回,但由于实例化过程是无限制的,因此无法停止。从抽象的数学角度来看,Prolog试图做的事情是明智的。直观地讲,将两个项合一的唯一方法是,如果将X实例化为包含无限长的father函子字符串的项,则抵消了多余的father函子在左侧的影响。但是我们使用的项是有限实体。无限项是一种有趣的数学抽象,但我们无法使用它们。无论Prolog多么努力,它都永远无法建立。

现在,像这样使Prolog耗尽内存很烦人,而且复杂的Prolog实现已经找到了更优雅地应对的方法。尝试对SWI Prolog或SICStus Prolog提出查询father(X)=X。答案将是这样的:

X = father(X).

也就是说,这些实现坚持认为可能实现合一,但是它们并没有陷入像天真的实现那样实际尝试为X实例化有限项的陷阱。取而代之的是,他们检测到存在潜在的问题,停止,声明可能的合一,并打印出无限项的有限表示形式,例如

father(X).

在上一个查询中。您可以用这些无限项的有限表示来计算吗?这取决于实现方式。在某些系统中,您无法对它们做很多事情。例如,构成查询

?- X = father(X), Y = father(Y), X = Y.

不会导致崩溃(请注意,X = Y要求我们合一两个无限项的有限表示)。但是,在某些现代系统中,合一可以很好地与此类表示形式协同工作(例如,SWI和Sicstus都可以处理前面的示例),因此您可以在程序中实际使用它们。但是,为什么可能要使用这样的表示以及这些表示的实际含义超出了本书的范围。

简而言之,对于“father(X)是否与X合一”这个问题,实际上有三种不同的回答。标准的合一算法给出了答案(也就是说,没有),较旧的Prolog实现的响应(会无休止的运行直到耗尽可用内存),而优化的Prolog实现给出的答案(即说是,并返回一个无限项的有限表示)。简而言之,这个问题没有“正确”的答案。重要的是要了解标准合一和Prolog合一之间的区别,并了解与您一起使用的Prolog实现如何处理此类示例。

现在,在本章结尾的实践环节中,我们要求您使用Prolog解释器尝试这些示例。在这里,我们想谈谈Prolog合一和标准合一之间的区别。考虑到他们处理此示例的方式非常不同,似乎标准的合一算法和Prolog合一方法本质上是不同的。其实不是。两种算法之间有一个简单的区别,就是它们面临着合一诸如X和father(X)之类的任务时的不同行为。当给定两个项来合一一个标准算法时,首先执行所谓的发生检查。 这意味着,如果要求将变量与项合一,则首先检查该变量是否出现在项中。如果是这样,则标准算法将声明不可能进行合一,因为显然是father(X)中存在变量X导致了前面讨论的问题。仅当项中未出现变量时,标准算法才会尝试执行合一。

换句话说,标准的合一算法是悲观的,它们首先执行事件检查,只有在确定状态安全后才继续进行并实际上尝试合一项。因此,标准的合一算法将永远不会陷入不断尝试实例化变量或必须诉诸无限项的情况。

另一方面,Prolog是乐观的。它假定您不会给它任何危险。因此,这是一个捷径:它省略了发生检查。只要您给它两个项,它就会立即出现并尝试合一它们。由于Prolog是一种编程语言,因此这是一种明智的策略。合一是使Prolog发挥作用的基本过程之一,因此需要尽快进行。每次要求合一时执行一次事件检查会大大降低它的速度。悲观策略是安全的,但乐观策略要快得多!仅当您(程序员)要求Prolog进行诸如将X与father(X)合一之类的操作时,Prolog才会遇到问题。这是在编写真实程序时,您不可能(有意)要求它执行类似的操作。

最后一句话。 Prolog带有一个内置谓词,该谓词执行标准的合一(即,与“发生检查”进行合一)。谓词是

unify_with_occurs_check/2.

因此,如果我们提出查询

?- unify_with_occurs_check(father(X), X).

我们会得到答复 false。

合一编程

如前所述,合一是Prolog中的一项基本操作。它在Prolog证明搜索(我们将很快学习)中起关键作用,仅此一项就变得至关重要。但是,随着您更好地了解Prolog,很明显,合一本身就是有趣且重要的。确实,有时您可以仅通过使用复合项来定义有趣的概念来编写有用的程序。然后可以使用合一来提取所需的信息。

这是一个简单的示例,这要归功于Ivan Bratko。以下两行的知识库定义了两个谓词,即vertical / 1和horizontal / 1,这两个谓词分别指定直线是垂直还是水平:

vertical(line(point(X,Y), point(X,Z))).

horizontal(line(point(X,Y),point(Z,Y))).

现在,乍一看,这个知识库似乎太简单了,难以引起兴趣:它只包含两个事实,没有规则。但是请稍等:这两个事实是使用复合项表达的,而这些项又以复合项作为参数。实际上,项内部嵌套了三个级别的项。此外,最深层的参数都是变量,因此将以一般方式定义概念。也许它并不像看起来那么简单。让我们仔细看看。

在最底层,我们有一个包含函子point和两个参数的复合项。它的两个自变量旨在实例化为数字:point(X,Y)表示点的笛卡尔坐标。也就是说,X表示该点到某个固定点的水平距离,而Y表示到该同一固定点的垂直距离。

现在,一旦我们指定了两个不同的点,就指定了一条线,即它们之间的线。因此,代表点的两个复合项通过函子line捆绑在一起,成为另一个复合项的两个自变量。实际上,我们用一个复合项来表示一条线,它有两个自变量,它们本身就是复合项并代表点。我们正在使用Prolog的能力来构建复合项,以逐步构建概念的层次结构。

垂直和水平是线的属性。因此,谓词vertical和horizontal都采用一个表示一条线的自变量。 vertical / 1的定义只是说:在x坐标相同的两个点之间的线是垂直的。注意我们如何捕获Prolog中“相同x坐标”的效果:我们只是将相同的变量X用作表示点的两个复合项的第一个参数。

类似地,horizontal / 1的定义只是说:在两个具有相同y坐标的点之间的线是水平的。为了捕获“相同y坐标”的效果,我们使用相同的变量Y作为表示点的两个复数项的第二个自变量。

我们可以用这个知识库做什么?让我们看一些例子:

?- vertical(line(point(1,1),point(1,3))).

true.

这应该很清楚:查询与我们很少的知识库中的vertical / 1的定义相合一(特别是两点的表示具有相同的第一个参数),因此Prolog表示是。同样,我们有:

?- vertical(line(point(1,1),point(3,2))).

false.

该查询与vertical / 1的定义不合一(两个点的表示具有不同的第一个参数),因此Prolog回应false。

但是我们也可以问一些更一般的问题:

?- horizontal(line(point(1,1), point(2,Y))).

Y = 1.

这里的查询是:如果我们想要在(1,1)的点和x坐标为2的点之间有一条水平线,那么第二个点的y坐标应该是什么? Prolog正确地告诉我们y坐标应为1。如果,然后我们向Prolog询问第二种可能性(请注意;),则它告诉我们不存在其他可能性。

现在考虑以下几点:

?- horizontal(line(point(2,3),P)).

P = point(_6638, 3).

该查询是:如果我们想要(2,3)处的点与其他某个点之间的水平线,还可以允许哪些其他点?答案是:任何点的y坐标为3。请注意,答案第一个参数_6638是变量,这是Prolog告诉我们任何x坐标都可以的方式。

一个一般性的标识:结构是对最后一个查询给出的答案,即point(_6638,3)。也就是说,答案是一个复合项,代表一个复合的概念(即“ y坐标为3的任何点”)。该结构是使用合一构建的,没有其他任何东西:并未使用逻辑推理(特别是不要使用惯用的方式)来生成它。在Prolog编程中,合一构建结构是一个强大的思想,远比这个更简单的示例所暗示的强大得多。而且,当编写大量使用合一的程序时,它可能非常高效。我们将在第7章中讨论一个漂亮的例子 差异列表,用于实现Prolog的内置语法系统“定义子句语法”。

这种编程风格在重要概念具有自然层次结构的应用程序中特别有用(就像在上面的简单知识库中所做的那样),因为我们可以使用复合项来表示该结构并合一访问它。例如,这种工作方式在计算语言学中起着重要作用,因为有关语言的信息具有自然的层次结构(想想将句子分析为名词短语和动词短语,将名词短语分析为限定词和名词的方式,以及依此类推)。

证明搜索

现在我们已经了解了合一,现在可以学习Prolog如何实际搜索知识库以查看查询是否满足。也就是说,我们准备学习证明搜索。我们将通过一个简单的示例介绍所涉及的基本思想。

假设我们正在使用以下知识库

f(a).

f(b).

g(a).

g(b).

h(b).

k(X) :- f(X), g(X), h(X).

假设然后摆出查询

?- k(Y).

很显然,此查询只有一个答案,即k(b),但是Prolog如何精确地解决这个问题?让我们来看看。

Prolog阅读知识库,并尝试将k(Y)与事实或规则的开头合一。它从上至下搜索知识库,并在可能的情况下首先进行合一。这里只有一种可能性:它必须将k(Y)合一到规则k(X)的开头:-f(X),g(X),h(X)。

当Prolog将查询中的变量合一为事实或规则中的变量时,它将生成一个全新的变量(例如_G34)来表示共享变量。因此,原始查询现在显示为:

k(_G34)

而且Prolog知道

k(_G34):- f(_G34),g(_G34),h(_G34).

那么我们现在有什么呢?原始查询说:“我想找到一个具有k属性的个人”。该规则说:“如果个人具有属性f,g和h,则其具有属性k”。因此,如果Prolog可以找到具有f,g和h属性的个人,则它将满足原始查询。因此,Prolog用以下目标列表替换了原始查询:

f(_G34), g(_G34), h(_g34).

如果我们以图形的方式进行思考,那么到目前为止对查询过程的讨论可以变得更加简洁明了。考虑下图:

框中的所有内容都是查询或目标。特别是,我们最初的目标是证明k(Y),因此显示在顶部框中。当我们将k(Y)与知识库中的规则的开头合一时,使X Y和新的内部变量_G34共享值,并且剩下目标f(_G34),g(_G34),h(_G34),如图所示。

现在,只要有目标列表,Prolog就会尝试从左到右地逐一实现这些目标。最左边的目标是f(_G34),其内容为:“我想要一个拥有财产f的人”。这个目标可以实现吗? Prolog尝试通过从上到下搜索知识库来做到这一点。它发现与该目标一致的第一项是事实f(a)。这满足了目标f(_G34),我们还有两个目标。现在,当我们将f(_G34)合一为f(a)时,_G34被实例化为a,并且此实例化适用于目标列表中所有出现的_G34。所以列表现在看起来像这样:

g(a),h(a)

现在,我们的证明搜索的图形表示如下所示:

但是事实g(a)在知识库中,因此我们必须证明的第一个目标也得到满足。所以目标清单变成

h(a)

现在的图形表示是

但是没有办法满足最后的目标h(a)。我们在知识库中仅有的有关h的信息是h(b),而这不会与h(a)合一。

那么接下来会发生什么呢?好吧,Prolog判定它犯了一个错误,并检查它是否错过了将事实与知识库中的规则或标题结合在一起的任何可能方法。它通过返回图形表示中所示的路径,寻找替代方法来做到这一点。现在,知识库中没有其他可以与g(a)合一的东西,但是还有另一种合一f(_G34)的方式。在搜索中,可以通过多种替代方法将目标与知识库合一的点称为选择点。 Prolog会跟踪遇到的选择点,因此,如果选择错误,它可以退回到先前的选择点并尝试。此过程称为回溯,它是Prolog中证明搜索的基础。

因此,让我们继续我们的示例。 Prolog回溯到最后一个选择点。这是图形表示中目标列表的位置:

f(_G34),g(_G34),h(_G34).

Prolog现在必须重做这项工作。首先,它必须尝试通过在知识库中进行进一步搜索来重新满足第一个目标。它可以这样做:它看到可以通过将f(_G34)与f(b)合一来与知识库中的信息合一第一个目标。这满足了目标f(_G34)并将X实例化为b,因此剩余目标列表为

g(b),h(b).

g(b)是知识库中的一个事实,因此也满足了这一点,而没有列出目标:

h(b).

而且,这个事实也存在于知识库中,因此这个目标也得到了满足。因此,Prolog现在有一个空目标列表。这意味着现在已经证明了建立原始目标(即k(Y))所需的一切。因此,原始查询是可以满足的,此外,Prolog还发现了满足该查询必须要做的事情(即,将Y实例化为b)。

考虑一下如果我们随后通过键入以下内容寻求另一种解决方案会发生什么,这很有趣:

;

这迫使Prolog退回到最后一个选择点,尝试寻找另一种可能性。但是,没有其他选择点,因为没有其他可能性可以通过知识库中的子句合一h(b),g(b),f(_G34)或k(Y),因此Prolog不会回答。另一方面,如果还有其他涉及k的规则,Prolog可能会失败,并尝试以我们描述的完全求解方式使用它们:也就是说,通过从知识库的顶部到底部搜索,目标从左到右列表,并在失败时回溯到上一个选择点。

让我们看一下整个搜索过程的图形表示。需要一些一般性的结论,因为这样的表示是在Prolog中思考证明搜索的一种重要方式。

该图具有树的形式;实际上,这是我们所谓的搜索树的第一个示例。这些树的节点说出在证明搜索的各个步骤中必须满足哪些目标,并且边缘跟踪当前目标(即目标列表中的第一个)时进行的变量实例化。在知识库中合一为事实或规则的开头。仍然包含未满足目标的叶节点是Prolog失败的点(要么是因为它在路径上某处做出了错误的决定,要么是因为不存在解决方案)。目标列表为空的叶节点对应于可能的解决方案。沿着路径的边缘 根节点到成功的叶节点将告诉您满足原始查询所需的变量实例化。

让我们看另一个例子。假设我们正在使用以下知识库:

loves(vincent,mia).

loves(marcellus,mia).

jealous(A,B):- loves(A,C), loves(B,C).

现在我们提出查询

?- jealous(X,Y).

查询的搜索树如下所示:

只有一种可能的方法可以将jealous(X,Y)与知识库进行合一,即使用规则

jealous(A,B):- loves(A,C), loves(B,C).

因此,必须满足的新目标是:

loves(_G5,_G6),loves(_G7,G6)

现在我们必须将loves(_G5,_G6)与知识库来合一。有两种方法可以执行此操作(可以将其与第一个事实或第二个事实合一),这就是路径此时分支的原因。在两种情况下,目标loves(_G7,mia)仍然存在,这也可以通过使用两个事实之一来满足。总而言之,有四个带有空目标列表的叶子节点,这意味着有四种方式可以满足原始查询。可以从根到叶节点的路径读取每个解决方案的变量实例。因此,四个解决方案是:

1. X = _G5 = vincent and Y = _G7 = vincent

2. X = _G5 = vincent and Y = _G7 = marcellus

3. X = _G5 = marcellus and Y = _G7 = vincent

4. X = _G5 = marcellus and Y = _G7 = marcellus

仔细研究此示例,并确保您理解它。

练习题

练习2.1、以下哪些对项是合一的?适当时,给出导致成功合一的变量实例。

1. bread = bread

2. ’Bread’ = bread

3. ’bread’ = bread

4. Bread = bread

5. bread = sausage

6. food(bread) = bread

7. food(bread) = X

8. food(X) = food(bread)

9. food(bread,X) = food(Y,sausage)

10. food(bread,X,beer) = food(Y,sausage,X)

11. food(bread,X,beer) = food(Y,kahuna_burger)

12. food(X) = X

13. meal(food(bread),drink(beer)) = meal(X,Y)

14. meal(food(bread),X) = meal(X,drink(beer))

练习2.2、我们正在使用以下知识库:

house_elf(dobby).

witch(hermione).

witch(’McGonagall’).

witch(rita_skeeter).

magic(X):- house_elf(X).

magic(X):- wizard(X).

magic(X):- witch(X).

满足以下哪些查询?适当时,给出导致成功的所有变量实例。

1. ?- magic(house_elf).

2. ?- wizard(harry).

3. ?- magic(wizard).

4. ?- magic(’McGonagall’).

5. ?- magic(Hermione).

绘制查询magic(Hermione)的搜索树。

练习2.3、这是一个很小的词典(即有关单个单词的信息)和一个语法规则组成的迷你语法:(由一个句子规则将句子定义为由五个单词组成的实体,按以下顺序排列:确定词,名词,动词,确定词,名词)。

word(determiner,a).

word(determiner,every).

word(noun,criminal).

word(noun,'big kahuna burger').

word(verb,eats).

word(verb,likes).

sentence(Word1,Word2,Word3,Word4,Word5):-

word(determiner,Word1),

word(noun,Word2),

word(verb,Word3),

word(determiner,Word4),

word(noun,Word5).

为了找出语法可以生成哪些句子,您必须提出什么查询?列出该语法可以按Prolog生成顺序的所有句子。

练习2.4、这是六个意大利语单词:

astante, astoria, baratto, cobalto, pistola, statale.

它们将以填字游戏的方式排列在以下网格中:

以下知识库表示一个包含这些词的词典:

word(astante, a,s,t,a,n,t,e).

word(astoria, a,s,t,o,r,i,a).

word(baratto, b,a,r,a,t,t,o).

word(cobalto, c,o,b,a,l,t,o).

word(pistola, p,i,s,t,o,l,a).

word(statale, s,t,a,t,a,l,e).

编写谓词填字游戏/ 6,告诉我们如何填充网格。前三个自变量应该是从左到右的垂直词,后三个自变量应该是从上到下的水平词。

4  实践环节

在此阶段,您应该已经初次体验了运行Prolog程序。第二期实践课程的目的是建议两组键盘练习,以帮助您熟悉Prolog的工作方式。第一组与合一有关,第二组与证明搜索有关。

首先,启动您的Prolog解释器。也就是说,获得一个显示通常的“我准备开始”提示的屏幕,该提示可能类似于:

?-

验证对练习2.1 合一示例的回答。您无需查阅任何知识库,只需直接询问Prolog,是否可以使用内置的= / 2谓词来合一项。例如,要测试food(bread,X)和food(Y,sausage)是否合一,只需输入

food(bread,X) = food(Y,sausage).

然后点击回车。

您还应该查看当您的Prolog实施尝试合一无法进行合一检查的项时,会发生什么情况。例如,查看当您给它以下查询时会发生什么:

g(X,Y) = Y.

如果可以处理此类示例,请尝试文本中提到的更棘手的示例:

X = f(X), Y = f(Y), X = Y.

一旦进行了尝试,就该继续尝试新的东西了。还有另一个内置的Prolog谓词,用于回答有关合一的查询,即\ = / 2(即:2字符谓词\ =)。这以与= / 2谓词相反的方式起作用:当其两个参数不合一时,它成功。例如,术语a和b不能合一,这说明了以下对话:

?- a \= b.

true.

通过(至少)在以下示例上进行尝试,确保您了解\ = / 2的工作方式。但这要主动而不是被动地进行。也就是说,在您输入示例之后,请暂停并尝试自己计算出Prolog将要响应的内容。然后只有按回车键才能确定您是否正确。

1. a \= a

2. ’a’ \= a

3. A \= a

4. f(a) \= a

5. f(a) \= A

6. f(A) \= f(a)

7. g(a,B,c) \= g(A,b,C)

8. g(a,b,c) \= g(A,C)

9. f(X) \= X

因此,\ = / 2谓词(基本上)是= / 2谓词的否定:当涉及一个谓词的查询不满足时,将满足该查询,而反之亦然。这是我们看到的第一个处理否定的Prolog机制的例子。我们将在第10章中讨论Prolog否定(及其特殊性)。

现在该继续介绍Prolog中最有用的工具之一:跟踪。这是一个内置的Prolog谓词,可更改Prolog的运行方式:强制Prolog一次仅执行一步查询,指示其在每一步中的操作。 Prolog等待您按回车键,然后再继续进行下一步,以便您可以准确了解正在发生的情况。它实际上是设计用作调试工具,但在学习Prolog时也很有用:使用trace单步执行程序是了解Prolog证明搜索工作原理的绝佳方法。

让我们看一个例子。在本文中,我们研究了对以下知识库进行查询k(Y)时涉及的证明搜索:

f(a).

f(b).

g(a).

g(b).

h(b).

k(X):- f(X), g(X), h(X).

假设此知识库位于文件proof.pl中。我们先咨询一下:

?- [proof].

true.

然后,我们键入trace,然后加上句号,然后按回车键:

?- trace.

true.

Prolog现在处于跟踪模式,并将逐步评估所有查询。例如,如果我们摆出查询k(X),然后每次Prolog返回?时都按回车键,我们将获得(类似)以下内容:

[trace]  ?- k(X).

Call: (8) k(_12522) ? creep

Call: (9) f(_12522) ? creep

Exit: (9) f(a) ? creep

Call: (9) g(a) ? creep

Exit: (9) g(a) ? creep

Call: (9) h(a) ? creep

Fail: (9) h(a) ? creep

Redo: (9) f(_12522) ? creep

Exit: (9) f(b) ? creep

Call: (9) g(b) ? creep

Exit: (9) g(b) ? creep

Call: (9) h(b) ? creep

Exit: (9) h(b) ? creep

Exit: (8) k(b) ? creep

X = b.

[trace]  ?-

请仔细研究。也就是说,尝试自己做同样的事情,并将此输出与本文中示例的讨论相关,尤其是与搜索树中的节点相关。为了让您入门,我们将在第8行指出查询中的变量(错误地)实例化为a的位置。标为“fail”的第一行是Prolog意识到其走错了路线并开始回溯的地方,标为“重做”的行是尝试为目标f(_12522)替代的地方。

在学习Prolog时,请使用跟踪并大量使用它。这是学习的好方法。哦,是的:您还需要知道如何关闭跟踪。只需键入notrace(后跟句号),然后按回车即可:

[trace]  ?- notrace.

true.

[debug]  ?-

现在学 Prolog 合一和证明搜索相关推荐

  1. Learn Prolog Now 翻译 - 第二章 - 合一和证明搜索 - 第一节, 合一

    Learn Prolog Now 翻译 - 第二章 - 合一和证明搜索 - 第一节, 合一 内容提要: 合一的定义: 一些合一的例子: 触发校验: 使用合一编程: 合一的定义 在上一章的知识库KB4中 ...

  2. 百善计算机学习,党建引领学做合一,志愿服务助力乡村振兴——计算机工程学院开展“百善孝为先”主题宣传文化墙墙绘涂鸦活动...

    为充分发挥党员的先锋模范作用和当代大学生学做合一的品质,用实际行动助力乡村振兴,2019年5月18日,计算机工程学院党员服务站组织学生党员及第57期党校积极分子赴郫都区郫筒街道长乐村开展"百 ...

  3. 中国人民大学文继荣:搜索,从相关性到有用性

    整理 | Mr Bear 在今年智源大会上,中国人民大学高领人工智能学院执行院长.北京智源人工智能研究院首席科学家文继荣教授以「从相关性到有用性」为线索,对搜索技术的发展历程以及未来的研究方向进行了梳 ...

  4. 2021年高考全国理科数学I卷数学压轴题的证明

    证明: 因为lim⁡x→0f(x)=0\lim\limits_{x \to 0} f(x)=0x→0lim​f(x)=0,是一个可去间断点,我们补充f(0)=0f(0)=0f(0)=0,使它在[0,e ...

  5. 路飞学城项目之课程搜索关键字接口及支付宝支付功能

    文章目录 1.课程之搜索关键字接口 2.支付宝支付功能 2.1.简单介绍 2.2.aliapy二次封装包 2.2.1.GitHub开源框架 2.2.2.依赖 2.2.3.结构 2.2.4.alipay ...

  6. 音乐搜索器 多站合一_分享一堆可以免费听音乐的良心网站!赶紧码住哦

    哈喽,我是二哈君,这次是轮到我给大家安利资源了.这次我带来的是一堆可以免费听音乐和下载音乐的网站,下面我们就来一起看下吧. 一.墨灵音乐 地址:music.mli.im 墨灵音乐是一款界面简洁干净.无 ...

  7. IT不行了?看看他们晒出的“证明”?

    总是听见网上不少人说IT饱和了,走下坡路了,找不到工作了,失业了找不到工作~ 最近有大量网友频繁晒出自己收到offer的喜讯!并且都说互联网正在回暖! 网友们的就业喜讯 滑动查看(沾沾喜气) ↓↓↓ ...

  8. Java入坑指南,学Java需要具备哪些前提条件?

    很多零基础的学员对于学Java比较迷茫,想通过学Java掌握一技之长,却不知道入门需要具备哪些条件?不知道怎么去学习? 下面详细来和大家聊聊该怎么学习Java: 首先,要对Java语言感兴趣,兴趣是最 ...

  9. 传智教育|2022最新版Java学习路线图全集汇总——Java学习到底学什么?一文详解

    2022版Java学习路线图来了! 每一年的Java学习路线图都会根据当前市场趋势做调整,也能更有针对性的对小伙伴的Java学习之旅提供帮助,这次小智给大家带来了2022最新版的Java学习路线图,内 ...

最新文章

  1. 明明程序员很累,为什么还有这么多人想入行?
  2. java编程石头剪刀布图片_石头、剪刀、布!10分钟带你打开深度学习大门,代码已开源...
  3. 前端小项目:使用canvas绘画哆啦A梦
  4. 扩展cocos slider控件,支持禁用置灰
  5. 1.5 测试php解析
  6. js原型、原型链、作用链、闭包全解
  7. 因为你的电脑安装了即点即用_即你所爱
  8. armbian nginx 部署博客_通过Git将Hexo博客部署到服务器
  9. deep learning for symbolic mathematics论文梳理
  10. 运行linux中degui_Windows与Linux合二为一?终于能在windows上运行Linux了!
  11. SciTE AMPL配置问题
  12. 《终身成长》卡罗尔 德韦克_epub+mobi+azw3
  13. OSChina 周三乱弹 ——祖传的程序员?????
  14. GG修改服务器迷你世界,gg修改器脚本大全迷你世界设置回点
  15. 9月18日博文阅读数异常波动公告
  16. this java 错误_java异常错误处理
  17. CyberSecurity Knowledge Base笔记
  18. 347.前K个高频元素 C++
  19. 收藏的软件测试学习资源
  20. 不用TTL线,OpenWrt刷回原厂或其他系统方法 841n测试通过

热门文章

  1. 超低功耗 段码LCD液晶显示驱动IC-VKL060 SSOP24 15SEG*4COM,超低工作电流约7.5微安,多用于传感器/水电表/工控仪表等
  2. C语言实现超长整数减法
  3. Elasticsearch——分页查询FromSize VS scroll
  4. 一套适用于所有老板的营销方案,让他一个月就卖出3780箱红酒!
  5. 智能制造业也开始进军元宇宙?
  6. 实现WIN CE下截屏并且保存到文件
  7. PHP 手机靓号规则、正则匹配、号码查询
  8. AFL学习笔记(下)
  9. 【牛腩】过程或函数 ‘news_selectByCaId‘ 需要参数 ‘@caid‘,但未提供该参数
  10. 表格下划线怎么去掉html,怎么去掉这该死的下划线?_html/css_WEB-ITnose