SWI-Prolog:理解无限循环

时间:2018-04-21 09:31:19

标签: prolog

我目前正在尝试了解prolog的基础知识。

我有这样的知识库: p(a). p(X) :- p(X). 如果我输入查询p(b),则与事实的统一将失败,并且使用规则p(X) :- p(X)导致统一事实再次失败。为什么规则在此之后反复应用?在这一点上,无法通过prolog返回false吗? 一段时间后,我收到消息"超出时间限制"。 我不太清楚为什么prolog会一遍又一遍地使用规则,但既然如此,我就不明白为什么我会得到不同的错误信息,如下例所示。 要清楚,我确实理解" p(X)如果p(X)"是一个不合理的规则,但我想了解那里到底发生了什么。

如果我有这样的知识库: p(X) :- p(X). p(a). 即使使用p(a)也没有机会得出结果,因为事实低于规则并且规则被一遍又一遍地调用。对于这个变体,我几乎立即收到一个不同的错误消息"错误:超出本地堆栈"这是可以理解的。

现在我的问题 - 这些案件有什么区别? 为什么我收到不同的错误消息?为什么在上述情况下第一次应用规则后prolog没有返回false?我的想法是,在上面的情况下,每次调用规则时都会重新启动过程,在下面的情况下,相同的过程会一遍又一遍地调用规则。如果有人能详细说明,我将不胜感激。

更新:如果我查询p(a).到第二个KB,就像我收到的那样"超出本地堆栈",但如果我查询p(b).来我获得相同的KB"超出时间限制"。这对我来说更加困惑,不应该与无限循环无关?

2 个答案:

答案 0 :(得分:2)

让我们首先考虑两个例子共同的以下程序片段:

p(X) :- p(X).

正如您正确指出的那样,很明显没有特定的解决方案被这个片段单独描述。在声明中,我们可以将其读作:“如果p(X)成立,则p(X)成立”。好的,所以我们不能只从这个条款中推断出任何具体的解决方案。

这解释了如果仅考虑此片段,p(b)无法保持的原因。此外,p(a)也不暗示p(b),因此无论您将事实p(a)置于何处,您都不会从这两个条款中获得p(b)

程序性,Prolog仍然尝试查找p(X)成立的案例。因此,如果您将?- p(X).作为查询发布,Prolog将尝试找到解决方案的反驳,无视它已经“尝试过”的内容。因此,它会尝试反复证明p(X)。 Prolog的默认解析策略SLDNF解决方案不保留已经尝试过哪些分支的内存,并且由于这个原因可以非常有效地实现,与其他编程语言相比,开销很小。

无限推理尝试与超出本地堆栈错误错误之间的差异只能通过考虑Prolog如何执行这些片段而在程序上理解。

Prolog系统通常应用称为尾调用优化的优化。如果不再有选择点,则这是适用的,这意味着它可以丢弃(或重用)现有的堆栈帧

两个示例之间的关键区别显然是 你添加了这个事实:在递归子句之前或之后。

在您的情况下,如果递归子句 last ,则在调用目标p(X)时不再有选择点。因此,可以重用或丢弃现有的堆栈帧。

另一方面,如果您首先编写递归子句 ,然后查询?- q(X).(或?- q(a).),那么两个子句是适用的,Prolog通过创建一个选择点来记住这一点。当调用递归目标时,选择点仍然存在,因此堆栈帧会堆积,直到它们超出可用限制。

如果您查询?- p(b).,那么参数索引会检测到p(a) 不适用,并且仅适用递归条款,独立于无论你是在事实之前还是之后写的。这解释了查询p(X)(或p(a))和p(b)(或其他查询)之间的区别。请注意,Prolog实现在其索引机制的强度方面存在差异。无论如何,你应该期望你的Prolog系统至少在第一个参数的最外面的functor和arity上编制索引。如有必要,可以在此机制之上手动构建更复杂的索引方案。 Modern Prolog系统提供 JIT索引,深度索引和其他机制,因此它们通常会自动检测适用的子句的确切子集。

请注意,有一种特殊形式的分辨率称为 SLG分辨率,在这种情况下,您可以使用它来改善程序的终止属性。例如,在SWI-Prolog中,您可以通过在程序之前添加以下指令来启用SLG解析:

:- use_module(library(tabling)).

:- table p/1.

使用这些指令,我们获得:

?- p(X).
X = a.

?- p(b).
false.

这与您期望定义的声明性语义相吻合。其他几个Prolog系统提供类似的设施。

答案 1 :(得分:1)

通过研究如何实现标准repeat/0,应该容易理解无限循环的概念:

repeat.
repeat :- repeat.

这将创建无限数量的选择点。第一个子句repeat.仅允许一次性执行。第二个子句repeat :- repeat.使它无限深度地递归。

添加任意数量的参数:

repeat(_, _, ..., _).
repeat(Param1, Param2, ..., ParamN) :- repeat(Param1, Param2, ..., ParamN).

您可能已将主体添加到这些子句中,并且第一类的参数具有有意义的名称,具体取决于您要归档的内容。如果主体不包含直接或从使用的谓词继承的切割,则也将像repeat/0一样,成为无限循环。