Pen*_*ope 5 logic prolog negation negation-as-failure
在许多 Prolog 指南中,以下代码用于说明 Prolog 中的“失败否定”。
not(Goal) :- call(Goal), !, fail.
not(Goal).
Run Code Online (Sandbox Code Playgroud)
然而,这些相同的教程和文本警告说,这不是“逻辑否定”。
问题:有什么区别?
我试图进一步阅读这些文本,但它们没有详细说明其中的区别。
逻辑主张:“有一只黑天鹅”。
Prolog 声称:“我发现了一只黑天鹅”。
这是一个强有力的主张。
逻辑否定:“不存在黑天鹅”。
Prolog 否定:“我还没有发现黑天鹅”。
没有那么强烈的主张;逻辑版本没有黑天鹅的空间,Prolog 版本确实有空间:代码中的错误,质量差的代码不能到处搜索,有限的资源限制搜索整个宇宙到天鹅大小的区域。
逻辑否定不需要任何人去寻找任何地方,该主张独立于任何证据或反驳。Prolog 逻辑纠结于 Prolog 可以使用您编写的代码证明什么,不能证明什么。
我喜欢@TesselationHeckler 的回答,因为它指出了问题的核心。您可能仍然想知道,这对于 Prolog 来说更具体意味着什么。考虑一个简单的谓词定义:
p(something).
Run Code Online (Sandbox Code Playgroud)
从根本上讲,我们得到了查询的预期答案:
?- p(something).
true.
?- \+ p(something).
false.
?- p(nothing).
false.
?- \+ p(nothing).
true.
Run Code Online (Sandbox Code Playgroud)
当变量和替换发挥作用时,问题就开始了:
?- \+ p(X).
false.
Run Code Online (Sandbox Code Playgroud)
p(X)并不总是假的,因为它p(something)是真的。到目前为止,一切都很好。让我们使用相等来表达替换并检查是否可以通过\+ p(nothing)这种方式导出:
?- X = nothing, \+ p(X).
X = nothing.
Run Code Online (Sandbox Code Playgroud)
从逻辑上讲,目标的顺序并不重要。但是当我们想要导出重新排序的版本时,它失败了:
?- \+ p(X), X = nothing.
false.
Run Code Online (Sandbox Code Playgroud)
不同之处在于X = nothing, \+ p(X),当我们到达那里的否定时,我们已经统一了,X以便 Prolog 尝试推导\+p(nothing)我们知道是真的。但在另一个顺序中,第一个目标是更一般的目标\+ p(X),我们看到它是错误的,让整个查询失败。
这当然不应该发生——在最坏的情况下,我们会期望不终止,但永远不会失败而不是成功。
因此,我们不能再依赖对子句的逻辑解释,而必须在涉及否定时立即考虑 Prolog 的执行策略。
有几个原因,
一个目标not(Goal_0)将会失败,当且仅当在执行该目标Goal0的时间点成功。因此,它的含义取决于执行该目标时恰好出现的实例。改变目标的顺序可能会改变结果。所以合取是不可交换的。not/1not/1
有时,可以通过重新编写实际查询来解决此问题。
防止错误答案的另一种方法是检查目标是否已充分实例化,方法是检查 sayground(Goal_0)是否为真,否则会产生实例化错误。这种方法的缺点是经常会产生实例化错误,而人们不喜欢它们。
甚至还有一种方法是适当延迟执行Goal_0。提高这种方法的粒度的技术称为构造性否定。您会发现很多关于它的出版物,但它们还没有进入通用 Prolog 库。原因之一是,当存在许多延迟目标时,此类程序特别难以调试。
当将 Prolog 的否定与约束结合起来时,事情会变得更糟。想想X#>Y,Y#>X哪个没有解决方案,但not/1只是看到它的成功(即使这种成功是有条件的)。
如果普遍否定,Prolog 认为存在一个最小模型的观点就不再成立。只要只考虑分层程序,这就不是问题。但是有许多程序没有分层但仍然是正确的,例如实现否定的元解释器。一般情况下有几个最小模型。解决这个问题远远超出了 Prolog 的范围。
学习Prolog时首先要坚持纯粹、单调的部分。这部分比许多人预期的要丰富得多。无论如何,你都需要掌握这一部分。