如何避免在Prolog中使用assert和retractall来实现全局(或状态)变量

use*_*622 6 prolog dcg prolog-assert implicit-state-passing

我往往最终写入的Prolog代码涉及(在整个程序或状态的重要信息)的一些算术计算,通过:首先获得存储在谓词的值,然后重新计算的值,最后使用存储所述值的装置retractallassert因为在Prolog中,我们不能使用两次赋值给变量is(因此几乎每个需要修改的变量都是全局的).我已经知道这在Prolog中不是一个好习惯.在这方面,我想问:

  1. 为什么在Prolog中这是一个不好的做法(虽然我自己不喜欢通过上面提到的步骤只是为了拥有一种灵活的(可修改的)变量)?

  2. 有哪些一般方法可以避免这种做法?小例子将不胜感激.

PS我刚开始学习Prolog.我确实有C语言的编程经验.

编辑进一步澄清

我想说的一个不好的例子(在win-prolog中)如下:

:- dynamic(value/1).
:- assert(value(0)).

adds :- 
   value(X),
   NewX is X + 4,
   retractall(value(_)),
   assert(value(NewX)).

mults :-
   value(Y),
   NewY is Y * 2,
   retractall(value(_)),
   assert(value(NewY)).

start :-
   retractall(value(_)),
   assert(value(3)),
   adds,
   mults,
   value(Q),
   write(Q).
Run Code Online (Sandbox Code Playgroud)

然后我们可以查询如下:

?- start.
Run Code Online (Sandbox Code Playgroud)

在这里,它非常简单,但在实际程序和应用中,上面显示的全局变量方法变得不可避免.有时候,上面给出的列表就像assert(value(0))... 一样长,并且有更多的断言谓词来定义更多的变量.这样做是为了使不同功能之间的值的通信成为可能,并在程序运行期间存储变量状态.

最后,我想再知道一件事:尽管您提出了各种解决方案以避免它,但上述做法何时变得不可避免?

mat*_*mat 6

为了避免这种情况的一般方式是可以考虑这样的国家间关系的计算的:您可以使用一个参数认为是有关您的程序状态之前的计算,以及描述该状态的第二个参数后,一些计算.例如,要描述对值的一系列算术运算V0,您可以使用:

state0_state(V0, V) :-
    operation1_result(V0, V1),
    operation2_result(V1, V2),
    operation3_result(V2, V).
Run Code Online (Sandbox Code Playgroud)

注意状态(在您的情况下:算术值)如何穿过谓词.命名约定V0- > V1- > ... - > V可以轻松扩展到任意数量的操作,并有助于记住这V0是初始值,并且V是应用各种操作后的值.需要访问或修改状态的每个谓词都有一个参数,允许您将状态传递给它.

像这样通过线程化状态的一个巨大优势是,您可以轻松地单独推断每个操作:您可以测试它,调试它,使用其他工具进行分析等,无需设置任何隐式全局状态.作为另一个巨大的好处,如果您使用足够的通用谓词,则可以在更多方向上使用您的程序.例如,您可以问:哪些初始值会导致给定的结果?

?- state0_state(V0, given_outcome).
Run Code Online (Sandbox Code Playgroud)

当使用命令式样式时,这当然是不可能的.因此,您应该使用约束而不是is/2,因为is/2只能在一个方向上工作.约束更容易使用,也是低级算术的更通用的现代替代方案.

动态数据库也比变量中的状态线程慢,因为它对每个变量执行索引等assertz/1.