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