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".