Prolog 的逻辑更新视图如何用于断言和收回?
How Prolog's logical update view works for assert and retract?
有人可以详细解释一下关于断言和撤回的 Prolog 逻辑视图吗?
例如,在下面的代码中,第一个 运行 Prolog returns 为 true,随后的 运行s returns false。我不知道为什么当 asserta(nextBound(100))
满足时,因为 Prolog 逻辑视图,nice(X) 在开始时仍然被值冻结,所以这个更改应该被忽略并且 nextbound(100)
必须是 false。
nextBound(10000).
nice(X) :-
asserta(nextBound(100)),
retract(nextBound(10000)),
nextBound(100).
您可以执行 trace
以确定会发生什么:
| ?- 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 开始,我们坚持逻辑更新视图,其中可回溯谓词 进入predicate 的定义不会看到任何变化(由 assert/1
或 retract/1
引起) 到 predicate.
换句话说,逻辑更新视图阻止谓词在执行时动态更改自身。 不是这里的情况。
事实上,在 Prolog 中很关键的一点是,在执行谓词期间,如果您在谓词中的某一点断言一个事实,则该结果必须立即在其中可见,否则谓词可能无法功能正常。有许多依赖此行为的常见库谓词。
从历史的角度来看,逻辑更新视图首先在Quintus 2.0(其当前的继任者是SICStus)中实现,并在1987年的文献中进行了描述。它已被ISO Prolog采用ISO/IEC 13211-1:1995。主要思想是动态谓词的任何目标都将准确地考虑在执行目标时出现的那些子句。任何进一步的更改 - 无论是添加还是删除 - 都不会被考虑用于执行该目标。
在逻辑更新视图之前,有各种或多或少一致的实现,大多数与 Prolog 系统的各种优化不兼容。请注意,差异仅在您的目标可能有多个答案时才会显示。要么作为一个简单的目标,要么在使用 retract and 时使用 retract/1
或 assertz/1
。仅使用 asserta/1
时不会显示差异。所以你的例子无法阐明差异。
考虑一个动态谓词 p/1
。由于以下交互仅使用顶层,我将通过断言一个事实并立即撤回 p/1
的所有事实来让系统知道 p/1
。此外,在开始下一个查询之前,我将使用 retractall(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) ...
这向您展示了一些背后的想法:从概念上讲,所有答案都是临时存储的,然后才显示每个答案。
呃,以上并不完全正确,因为只有 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).
有人可以详细解释一下关于断言和撤回的 Prolog 逻辑视图吗?
例如,在下面的代码中,第一个 运行 Prolog returns 为 true,随后的 运行s returns false。我不知道为什么当 asserta(nextBound(100))
满足时,因为 Prolog 逻辑视图,nice(X) 在开始时仍然被值冻结,所以这个更改应该被忽略并且 nextbound(100)
必须是 false。
nextBound(10000).
nice(X) :-
asserta(nextBound(100)),
retract(nextBound(10000)),
nextBound(100).
您可以执行 trace
以确定会发生什么:
| ?- 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 开始,我们坚持逻辑更新视图,其中可回溯谓词 进入predicate 的定义不会看到任何变化(由 assert/1
或 retract/1
引起) 到 predicate.
换句话说,逻辑更新视图阻止谓词在执行时动态更改自身。 不是这里的情况。
事实上,在 Prolog 中很关键的一点是,在执行谓词期间,如果您在谓词中的某一点断言一个事实,则该结果必须立即在其中可见,否则谓词可能无法功能正常。有许多依赖此行为的常见库谓词。
从历史的角度来看,逻辑更新视图首先在Quintus 2.0(其当前的继任者是SICStus)中实现,并在1987年的文献中进行了描述。它已被ISO Prolog采用ISO/IEC 13211-1:1995。主要思想是动态谓词的任何目标都将准确地考虑在执行目标时出现的那些子句。任何进一步的更改 - 无论是添加还是删除 - 都不会被考虑用于执行该目标。
在逻辑更新视图之前,有各种或多或少一致的实现,大多数与 Prolog 系统的各种优化不兼容。请注意,差异仅在您的目标可能有多个答案时才会显示。要么作为一个简单的目标,要么在使用 retract and 时使用 retract/1
或 assertz/1
。仅使用 asserta/1
时不会显示差异。所以你的例子无法阐明差异。
考虑一个动态谓词 p/1
。由于以下交互仅使用顶层,我将通过断言一个事实并立即撤回 p/1
的所有事实来让系统知道 p/1
。此外,在开始下一个查询之前,我将使用 retractall(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) ...
这向您展示了一些背后的想法:从概念上讲,所有答案都是临时存储的,然后才显示每个答案。
呃,以上并不完全正确,因为只有 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).