【问题标题】:How Prolog's logical update view works for assert and retract?Prolog 的逻辑更新视图如何用于断言和撤回?
【发布时间】:2015-01-23 18:18:45
【问题描述】:

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

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

nextBound(10000).

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

【问题讨论】:

    标签: prolog iso-prolog prolog-assert


    【解决方案1】:

    您可以通过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(_) 的整个第二次执行在那时失败,因为回溯assertaretract 不重新执行并产生额外的结果。

    列表显示现在有两个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 中至关重要的是,在执行谓词期间,如果您在谓词中的某一点断言事实,则该结果必须立即在其中可见,否则谓词可能无法正常运行.有很多常见的库谓词依赖于这种行为。

    【讨论】:

    • 谢谢先生,但我认为由于序言“nextBound(100)”的逻辑视图应该在第一次执行时通过 nice() 评估“假”,因为 asserta(nextBound(100))在 nice() 谓词中完成,并且 nice(_) 在那个时候被冻结了。不是吗?
    • @CoderInNetwork 正如您在跟踪中看到的那样,nextBound(100) 被评估并在nice(_) 的第一次执行中按预期成功,因为它在被查询之前已被断言。
    • @CoderInNetwork,需要明确的是,当 nice(_) 被执行时,它不会在数据库的 frozen 状态下运行。数据库在执行过程中是动态的。任何断言或撤回的事实都会立即完成,后续查询会看到这些结果。我回答你的问题了吗?
    • 亲爱的 lurker 你的答案很明确,但不是和这个缩水吗?swi-prolog.org/pldoc/man?section=update
    • @CoderInNetwork 我认为这并不矛盾。该链接上的评论说,从 SWI-Prolog 3.3.0 开始,我们坚持逻辑更新视图,其中 输入谓词定义的可回溯谓词将看不到任何更改(由assert/1retract/1) 到谓词 换句话说,谓词不能动态地改变它自己的定义。对于数据库中谓词的更改,谓词本身的定义是静态的。如果不是这样,那就有点乱了。
    【解决方案2】:

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

    在逻辑更新视图之前,已经有各种或多或少一致的实现,大多数与 Prolog 系统的各种优化不兼容。请注意,仅当您的目标可能有多个答案时,差异才会显示出来。无论是作为一个简单的目标,还是在使用retract 时,您使用的是retract/1assertz/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).
    

    【讨论】:

    • 感谢您添加此答案。历史信息和示例非常有帮助。 (+1)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 1970-01-01
    • 2019-01-06
    • 1970-01-01
    相关资源
    最近更新 更多