How to avoid using assert and retractall in Prolog to implement global (or state) variables

I often end up writing code in Prolog, which involves some arithmetic calculation (or status information important throughout the program), by first retrieving the value stored in the predicate, then recalculating the value, and finally storing the value using retractall and assert , because in Prolog we cannot assign values ​​to a variable twice using is (thereby making almost every variable that needs to be modified global). I found out that this is not a good practice in Prolog. In this regard, I would like to ask:

  • Why is this a bad practice in Prolog (although I myself do not like to go through the above steps to have some kind of flexible (mutable) variable)?

  • What are some common ways to avoid this practice? Small examples would be greatly appreciated.

PS I just started studying Prolog. I have programming experience in languages ​​like C.

Edited for further clarification

The wrong example (in win-proog) of what I want to say is given below:

 :- 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). 

Then we can request how:

 ?- start. 

This is very trivial here, but in the real program and application, the above method of the global variable becomes inevitable. Sometimes the list above, like assert(value(0)) ..., grows very long, and many others claim predicates to define more variables. This is done to ensure the exchange of data between various functions and the storage of variable states during program execution.

Finally, I would like to know one more thing: When does the practice mentioned above become inevitable, despite the various solutions you have proposed, to avoid this?

+6
source share
2 answers

A general way to avoid this is to think in terms of the relationship between the states of your calculations: you use one argument to store the state that is relevant to your program before the calculation, and a second argument that describes the state after some calculation. For example, to describe a sequence of arithmetic operations on a value of V0 , you can use:

 state0_state(V0, V) :- operation1_result(V0, V1), operation2_result(V1, V2), operation3_result(V2, V). 

Notice how the state (in your case: arithmetic value) penetrates through predicates. The naming convention V0 β†’ V1 β†’ ... β†’ V easily scales for any number of operations and helps keep in mind that V0 is the initial value and V is the value after applying various operations. Each predicate that needs to access or change state will have an argument that allows you to pass state to it.

The huge advantage of streaming state in such a way that you can easily talk about each operation separately: you can test it, debug it, analyze it with other tools, etc., without creating any implicit global state, As another huge benefit you can use your programs in wider ways, provided that you use fairly general predicates. For example, you may ask: what initial values ​​lead to a given result?

 ?- state0_state(V0, given_outcome). 

This, of course, is not always possible using the imperative style. Therefore, you should use restrictions instead of is/2 , because is/2 only works in one direction. Limitations are much easier to use and a more general modern alternative to low-level arithmetic.

A dynamic database is also slower than the state of threads in variables because it performs indexing, etc. on each assertz/1 .

+5
source

1 is bad practice because it destroys the declarative model that the (pure) Prolog programs show.

Then the programmer must think in procedural terms, and the Prolog procedural model is quite complex and difficult to give.

In particular, we should be able to resolve the issue of the validity of the approved knowledge, while the programs recede, i.e. follow alternative paths to those already tried, which (possibly) provoked allegations.

2 - We need additional variables to maintain state. A practical, maybe not very intuitive way, uses grammar rules (DCG) instead of simple predicates. Grammar rules are translated with the addition of two list arguments, usually hidden, and we can use these arguments to implicitly pass the state, and only refer / change it where necessary.

An interesting introduction here: DCGs in Prolog from Markus Triska. Find Implicitly passing states around : you will find this small illustrative example:

 num_leaves(nil), [N1] --> [N0], { N1 is N0 + 1 }. num_leaves(node(_,Left,Right)) --> num_leaves(Left), num_leaves(Right). 

More generally and for further practical examples, see "Thinking in the States" from the same author.

edit: usually, assert / retract is only required if you need to modify the database or track the result of the calculation when backtracking. A simple example from my (very) old Prolog interpreter :

 findall_p(X,G,_):- asserta(found('$mark')), call(G), asserta(found(X)), fail. findall_p(_,_,N) :- collect_found([],N), !. collect_found(S,L) :- getnext(X), !, collect_found([X|S],L). collect_found(L,L). getnext(X) :- retract(found(X)), !, X \= '$mark'. 

findall / 3 can be considered as the base of all predicate solutions . This code should be the same from the Clockins-Mellish - Programming in Prolog tutorial. I used it when testing the "real" findall / 3 i . You can see that this is not "reentrant," due to the alias "$ mark".

+4
source

Source: https://habr.com/ru/post/954598/