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.87Example 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))).
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.5. 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: