5

有人可以详细解释一下关于断言和撤回的 Prolog 逻辑视图吗?

例如在下面的代码中,Prolog 在第一次运行中返回 true,而在随后的运行中返回 false。我不知道为什么因为 asserta(nextBound(100))满足时的 Prolog 逻辑视图,nice(X) 在它开始时仍然被冻结,所以这个变化应该被忽略并且nextbound(100)必须是错误的。

nextBound(10000).

nice(X) :-
   asserta(nextBound(100)),
   retract(nextBound(10000)),
   nextBound(100).
4

2 回答 2

5

您可以执行 atrace来确定会发生什么:

| ?- nice(_).
      1    1  Call: nice(_17) ?
      2    2  Call: asserta(nextBound(100)) ?
      2    2  Exit: asserta(nextBound(100)) ?   <-- 1st assert of netBound(100) succeeds
      3    2  Call: retract(nextBound(10000)) ?
      3    2  Exit: retract(nextBound(10000)) ? <-- retract nextBound(10000) succeeds
      4    2  Call: nextBound(100) ?
      4    2  Exit: nextBound(100) ? <-- Succeeds because netBound(100) now a fact
      1    1  Exit: nice(_17) ?

(1 ms) yes
{trace}
| ?- nice(_).
      1    1  Call: nice(_17) ?
      2    2  Call: asserta(nextBound(100)) ?
      2    2  Exit: asserta(nextBound(100)) ?   <-- 2nd assert of netBound(100) succeeds
      3    2  Call: retract(nextBound(10000)) ?
      3    2  Fail: retract(nextBound(10000)) ? <-- retract nextBound(10000) fails
      1    1  Fail: nice(_17) ?

(3 ms) no
{trace}
| ?-

您可以看到,在第一种情况下,首先nextBound(100)成功断言了事实(第一次)。那么,retract(nextBound(10000))成功是因为nextBound(10000).是数据中存在的事实。之后,查询nextBound(100)成功,因为在这个事实之前的两个步骤被断言到数据中。

在第二次执行时nice(_),nextBound(10000)不存在,因为它在第一次执行中被收回,并且代码不会重新声明它。因此,在 , 的第二次执行中nice(_)失败,retract(nextBound(10000))因为事实nextBound(10000)不存在并且整个第二次执行nice(_)在该点失败,因为回溯asserta并且retract不重新执行并产生额外的结果。

清单显示现在有两个nextBound(100)事实,因为我们在两次运行中都断言了一个nice(_),并且没有,nextBound(10000)因为它在第一次运行中被撤回nice(_)

| ?- listing.

% file: user

nice(_) :-
        asserta(nextBound(100)),
        retract(nextBound(10000)),
        nextBound(100).

% file: user_input

nextBound(100).
nextBound(100).

(1 ms) yes
| ?-

如 SWI 文档中所述,逻辑更新视图说,从 SWI-Prolog 3.3.0 开始,我们坚持逻辑更新视图,其中进入谓词定义的可回溯谓词将看不到任何更改(由assert/1retract/1)到谓词

换句话说,逻辑更新视图防止谓词在执行时动态改变自身。这不是这里的情况。

事实上,在 Prolog 中至关重要的是,在执行谓词期间,如果您在谓词中的某一点断言事实,则该结果必须立即在其中可见,否则谓词可能无法正常运行。有许多常见的库谓词依赖于这种行为。

于 2015-01-23T18:42:26.660 回答
5

从历史的角度来看,逻辑更新视图首先在 Quintus 2.0(其当前的继任者是 SICStus)中实现,并在 1987 年的文献中进行了描述。它已在 ISO Prolog ISO/IEC 13211-1:1995 中采用。主要思想是,动态谓词的任何目标都将准确考虑执行目标时出现的那些子句。任何进一步的更改——无论是添加还是删除——都不会在执行该目标时考虑在内。

在逻辑更新视图之前,已经有各种或多或少一致的实现,大多数与 Prolog 系统的各种优化不兼容。请注意,仅当您的目标可能有多个答案时,差异才会显示出来。无论是作为一个简单的目标,还是在使用retract并且您正在使用retract/1or时assertz/1。仅使用时不会显示差异asserta/1。因此,您的示例无法阐明差异。

考虑一个动态谓词p/1。由于以下交互仅使用顶层,我将p/1通过断言一个事实并立即收回所有事实来让系统知道p/1retractall(p(_))此外,在开始下一个查询之前,我将删除所有事实。

?- asserta(p(1)).
true.  % now p/1 is known to the system.

?- retractall(p(_)), assertz(p(1)), p(X), assertz(p(2)).
X = 1.  % only one answer!

?- retractall(p(_)), assertz(p(1)), p(X), assertz(p(2)), p(Y).
X = 1, Y = X ;
X = 1,
Y = 2.

所以第一个目标p(X)只看到p(1),而第二个目标p(Y)两者都看到。这适用于任何主动目标:

?- retractall(p(_)), assertz(p(1)), assertz(p(2)), p(X), assertz(p(3)), p(Y).
X = 1, Y = X ;
X = 1,
Y = 2 ;
X = 1,
Y = 3 ;
X = 2,
Y = 1 ;
X = 2, Y = X ;
X = 2,
Y = 3 ;
X = 2,
Y = 3 ;
false.

同样,请注意X只有 1 或 2 而不是 3。

或者,您可以想象每个目标p(X)都替换为:

... findall(Xi, p(Xi), Xis), member(X, Xis) ...

这向您展示了一些背后的想法:从概念上讲,所有答案都是临时存储的,然后才显示每个答案。

呃,上面说的不太对,只有of的子句p/1这样处理的。也就是说,只要你只存储事实,上面的解释是完美的,但如果你还存储规则,你将需要一个更复杂的解释,大致:

 ... findall(Xi-Bi, clause(p(Xi),Bi), XiBis), member(X-B,XiBis), B ...

而且,再一次,这不是显而易见的事实,因为削减等一些更奇特的问题可能会介入。我将暂时保持这种状态1

同样,retract/1也会看到并删除它在执行时看到的子句。对于大多数情况,这是非常直观的,并且符合我们的期望。然而,也有以下相当荒谬的情况:

?- retractall(p(_)),
   assertz(p(1)), assertz(p(2)),
   retract(p(X)), ( X = 1, retract(p(Y)) ; X = 2, Y = none ).
X = 1,
Y = 2 ;
X = 2,
Y = none.

这里,事实p(2)被删除了两次,尽管数据库只包含一个事实p(2).


脚注

1 实际上,替换

... p(X) ...

经过

... findall(Xi-Bi, clause(p(Xi),Bi), XiBis), answs_goal_x(XiBis,X, G), G ...

answs_goal_x([], _, true).
answs_goal_x([Xi-Bi|XiBis], X, ( X = Xi, Bi ; G) ) :-
   answs_goal_x(XiBis, X, G).
于 2015-01-23T19:41:30.347 回答