The Dynamic Database

library(persistency) (SWI) journals dynamic predicates to disk:

17.1 Declaring and modifying

:- dynamic counter/1, visited/1.

counter(0).

increment :-
    retract(counter(N)),
    N1 is N + 1,
    assertz(counter(N1)).

?- assertz(fact(1)).      % add at the end
?- asserta(fact(0)).      % add at the beginning
?- retract(fact(0)).      % remove first matching clause
?- retractall(fact(_)).   % remove all matching clauses (succeeds even if none)
?- abolish(fact/1).       % remove predicate entirely
?- assertz((double(X, Y) :- Y is 2 * X)).   % assert a rule

17.2 Use cases

Caching / memoization (prefer tabling, §31).

Global state, counters, configuration.

Knowledge bases learned at runtime.

:- dynamic memo_fib/2.
mfib(N, F) :- memo_fib(N, F), !.
mfib(N, F) :-
    N < 2 -> F = N
    ;  N1 is N-1, N2 is N-2, mfib(N1, F1), mfib(N2, F2), F is F1+F2,
       assertz(memo_fib(N, F)).

17.3 Cautions

Assert/retract are not undone on backtracking — they break logical purity.

They make code harder to reason about and test.

In multi-threaded code, use thread_local predicates or transactions (transaction/1 in SWI 8.5+).

Alternatives: pass state as arguments, global variables (b_setval/nb_setval), tabling, library(assoc).

17.4 Persistence

library(persistency) (SWI) journals dynamic predicates to disk:

:- use_module(library(persistency)).
:- persistent user(name:atom, age:nonneg).
:- db_attach('users.db', []).
?- assert_user(ann, 30).