我目前正在尝试了解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"超出时间限制"。这对我来说更加困惑,不应该与无限循环无关?
答案 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
一样,成为无限循环。