

See also the howto page:
Did you know ... | Search Documentation: |
![]() | Managing (dynamic) predicates |
According to the ISO standard, abolish/1
can only be applied to dynamic procedures. This is odd, as for dealing
with dynamic procedures there is already retract/1
and retractall/1.
The abolish/1
predicate was introduced in DEC-10 Prolog precisely for dealing with
static procedures. In SWI-Prolog, abolish/1
works on static procedures, unless the Prolog flag iso
is set to true
.
It is advised to use retractall/1 for erasing all clauses of a dynamic predicate.
abolish(Name/Arity)
. The predicate abolish/2
conforms to the Edinburgh standard, while abolish/1
is ISO compliant.copy_predicate_clauses(From, To) :- head(From, MF:FromHead), head(To, MT:ToHead), FromHead =.. [_|Args], ToHead =.. [_|Args], forall(clause(MF:FromHead, Body), assertz(MT:ToHead, Body)). head(From, M:Head) :- strip_module(From, M, Name/Arity), functor(Head, Name, Arity).
user
and in
normal modules to redefine any system predicate. If the system
definition is redefined in module user
, the new definition
is the default definition for all sub-modules. Otherwise the
redefinition is local to the module. The system definition remains in
the module system
.
Redefining system predicate facilitates the definition of compatibility packages. Use in other contexts is discouraged.
bee
on backtracking despite the fact that bee
is already retracted.88Example by
Jan Burse
:- dynamic insect/1. insect(ant). insect(bee). ?- ( retract(insect(I)), writeln(I), retract(insect(bee)), fail ; true ). ant ; bee.
If multiple threads start a retract on the same predicate at the same
time their notion of the entry generation is adjusted such that
they do not retract the same first clause. This implies that, if
multiple threads use once(retract(Term))
, no two threads
will retract the same clause. Note that on backtracking over retract/1,
multiple threads may retract the same clause as both threads respect the
logical update view.
resource_error(program_space)
exception. The example below
adds two facts and a rule. Note the double parentheses around the rule.
?- assertz(parent('Bob', 'Jane')). ?- assertz(female('Jane')). ?- assertz((mother(Child, Mother) :- parent(Child, Mother), female(Mother))).
SWI-Prolog adheres to the logical update view, where backtrackable predicates that enter the definition of a predicate will not see any changes (either caused by assert/1 or retract/1) to the predicate. This view is the ISO standard. Logical updates are realised by keeping generation information on clauses. Each change to the database causes an increment of the generation of the database. Each goal is tagged with the generation in which it was started. Each clause is flagged with the generation it was created in as well as the generation it was erased. Only clauses with a‘created’ ...‘erased’interval that encloses the generation of the current goal are considered visible. The generation mechanism is also used to implement transactions See section 4.14.1.3.
Erased clauses are (eventually) reclaimed by the clause garbage
collector implemented by garbage_collect_clauses/0.
By default, the clause garbage collector runs in a thread named gc
,
together with the atom garbage collector (garbage_collect_atoms/0).
See also the Prolog flag gc_thread.
The indexing capabilities of SWI-Prolog are described in section 2.17. Summarizing, SWI-Prolog creates indexes for any applicable argument, pairs of arguments and indexes on the arguments of compound terms when applicable. Extended JIT indexing is not widely supported among Prolog implementations. Programs that aim at portability should consider using term_hash/2 and term_hash/4 to design their database such that indexing on constant or functor (name/arity reference) on the first argument is sufficient. In some cases, using the predicates below to add one or more additional columns (arguments) to a database predicate may improve performance. The overall design of code using these predicates is given below. Note that as term_hash/2 leaves the hash unbound if Term is not ground. This causes the lookup to be fast if Term is ground and correct (but slow) otherwise.
:- dynamic x/2. assert_x(Term) :- term_hash(Term, Hash), assertz(x(Hash, Term)). x(Term) :- term_hash(Term, Hash), x(Hash, Term).
This predicate may be used to build hash tables as well as to exploit argument indexing to find complex terms more quickly.
The hash key does not rely on temporary information like addresses of atoms and may be assumed constant over different invocations and versions of SWI-Prolog.90Last change: version 5.10.4 Hashes differ between big and little endian machines. The term_hash/2 predicate is cycle-safe.bugAll arguments that (indirectly) lead to a cycle have the same hash key.
HashKey is in the range [0 ...Range-1]. Range must be in the range [1 ... 2147483647].
This predicate raises an exception when trying to compute the hash on a cyclic term or attributed term. Attributed terms are not handled because subsumes_chk/2 is not considered well defined for attributed terms. Cyclic terms are not supported because this would require establishing a canonical cycle. That is, given A=[a|A] and B=[a,a|B], A and B should produce the same hash. This is not (yet) implemented.
This hash was developed for lookup of solutions to a goal stored in a table. By using a cryptographic hash, heuristic algorithms can often ignore the possibility of hash collisions and thus avoid storing the goal term itself as well as testing using =@=/2.
Traditionally, Prolog database updates add or remove individual clauses. The Logical Update View ensures that a goal that is started on a dynamic predicate does not see modifications due to assert/1 or retract/1 during its life time. See section 4.14.1.1. In a multi-threaded context this assumption still holds for individual predicates: concurrent modifications to a dynamic predicate are invisible.
Transactions allow running a goal in isolation. The goals running inside the transaction‘see’the database as it was when the transaction was started together with database changes done by the transaction goal. Other threads see no changes until the transaction is committed. The commit, also if it involved multiple clauses spread over multiple predicates, becomes atomically visible to other threads. Transactions have several benefits Wielemaker, 2013
Transactions on their own do not guarantee consistency. For example, when running the code below to update the temperature concurrently from multiple threads it is possible for the global state to have multiple temperature/1 clauses.
update_temperature(Temp) :- transaction(( retractall(temperature(_)), asserta(temperature(Temp)))).
Global consistency can be achieved by wrapping the above transaction using with_mutex/2 or by using transaction/3 with a constraint that demands a single clause for temperature/1
SWI-Prolog transactions only affect the dynamic database. Static predicates are globally visible and shared at all times. In particular, transactions do not affect loading source files and thus, source files loaded inside a transaction (e.g., due to autoloading) are immediately globally visible. This may pose problems if loading source files provide clauses for dynamic predicates.
Currently the number of database changes inside a transaction (or
snapshot, see snapshot/1)
is limited to 2 ** 32 -1. If this limit is exceeded a representation_error(transaction_generations)
exception is raised.
Transactions may be nested. The above mentioned limitation for the number of database changes applies to the combined number in nested transactions.
If Goal succeeds, the transaction is committed. This implies that (1) any clause that is asserted in the transaction and not retracted in the same transaction is made globally visible and (2) and clause the existed before the transaction and is retracted in the transaction becomes globally invisible. Multiple transactions may retract the same clause and be committed, i.e., committing a retract that was already performed is a no-op. All modifications become atomically visible to other threads. The transaction/3 variation allows for verifying constraints just before the commit takes place.
Clause ordering Inside a transaction clauses can be added using asserta/1 and assertz/1. If only a single transaction is active at any point in time transactions preserve the usual ordering of clauses. However, if multiple transactions manipulate the same predicate(s) concurrently (typically using transaction/3), the final order of the clauses is the order in which the transactions asserted the clauses and not the order in which the transactions are committed.
The transaction/1
variant is equivalent to transaction(Goal,[])
. The transaction/2
variant processed the following options:
true
, accumulate events from changes to dynamic
predicates (see prolog_listen/2)
and trigger these events as part of the commit phase. This implies that
if the transaction is not committed the events are never triggered.
Failure to trigger the events causes the transaction to be discarded.
Experimental.
once(Goal)
once(Constraint)
This predicate is intended to execute multiple transactions with a time consuming Goal in part concurrently. For example, it can be used for a Compare And Swap (CAS) like design. We illustrate this using a simple counter in the code below. Note that the transaction fails if some other thread concurrently updated the counter. This is why we need the repeat/0 and a final !/0. The CAS-style update is in general useful if Goal is expensive and conflicts are rare.
:- dynamic counter/1. increment_counter(Delta) :- repeat, transaction(( counter(Value), Value2 is Value+Delta, ), ( retract(counter(Value)), asserta(counter(Value2)) ), counter_lock), !.
Transactions interact with other facilities that depend on changing dynamic predicates. This section discusses these interactions.
last_modified_generation(Generation)
we can determine
whether a predicate was modified. When a predicate is changed inside a
transaction this generation is not updated. The generation for dynamic
predicates that are modified in the transaction is updated to the commit
generation when the transaction is committed. Asking for the last
modified generation inside the transaction examines the log of
modified clauses and reports the generation as one of
In other words: tables being reevaluated inside a transaction that do not depend on predicates modified inside the transaction remain valid. Monotonic tables that get new answers due to asserts inside the transaction have these answers removed during the rollback while the table remains valid. Monotonic tables that are for some reason invalidated inside the transaction are invalidated during the rollback.
Correct interaction between tabling and transaction currently only deals with local tables. Shared tables should not be combined with transactions. Future versions may improve on that. A possible route is to make a local copy from a shared table when (re)evaluation is performed inside a transaction.
Status SWI-Prolog transaction basics and API are stable. Interaction with other parts of the system that depend on dynamic predicates is still unsettled. Future versions may support non-determinism through transactions and snapshots.
See also the howto page: