Skip to content

Database Operations

Clausal supports runtime modification of the predicate database — adding and removing clauses while a program is running. This enables dynamic state, memoization, and self-modifying programs.


Quick Example

-dynamic(Color/1)

Color("red"),
Color("blue"),

Test("assert and query") <- (
    assertz(Color("green")),
    Color("green")
)  # nv

The -dynamic Directive

By default, predicates are locked after loading — you cannot add or remove clauses at runtime. To allow runtime modification, declare the predicate as dynamic (see Directives for other directive types):

-dynamic(Counter/1)
-dynamic(Cache/2)

Without this directive, assertz and retract will raise a permission error.


Adding Clauses

assertz/1

assertz(Term) — add a fact at the end of the clause list (like Prolog's assertz).

-dynamic(Fact/1)

Fact("init"),

Test("assert") <- (
    assertz(Fact(42)),
    Fact(42)
)  # nv

asserta/1

asserta(Term) — add a fact at the beginning of the clause list (like Prolog's asserta). The new clause will be tried first on subsequent queries.

-dynamic(Priority/1)

Priority("low"),

Test("assert first") <- (
    asserta(Priority("high")),
    Priority("high")
)  # nv

Removing Clauses

retract/1

retract(Term) — remove the first clause whose head unifies with Term.

-dynamic(Item/1)

Item("a"),
Item("b"),
Item("c"),

Test("retract") <- (
    retract(Item("b")),
    (not Item("b")),
    Item("a"),
    Item("c")
)  # nv

retract uses unification for matching, so you can retract by pattern:

-dynamic(Pair/2)

Pair("x", 1),
Pair("y", 2),
Pair("z", 3),

Test("retract by pattern") <- (
    retract(Pair("y", _)),
    (not Pair("y", 2))
)  # nv

Table Management

abolish_table/2

abolish_table(functor, Arity) — clear cached answers for a specific tabled predicate.

-table(MemoFib/2)

Test("clear") <- abolish_table("MemoFib", 2)  # nv

abolish_all_tables/0

abolish_all_tables() — clear all tabling caches at once.

Test("clear all") <- abolish_all_tables()

Patterns & Recipes

Memoization

Use assert to cache computed results:

-dynamic(FibCache/2)

Fib(0, 0),
Fib(1, 1),
Fib(N, F) <- (
    N > 1,
    N1 is N - 1,
    N2 is N - 2,
    Fib(N1, F1),
    Fib(N2, F2),
    F is F1 + F2,
    assertz(FibCache(N, F))
)

(For automatic memoization, consider -table instead.)

Counter / mutable state

-dynamic(Ctr/1)

Ctr(0),

Increment(NEW) <- (
    retract(Ctr(OLD)),
    NEW is OLD + 1,
    assertz(Ctr(NEW))
)

Test("counter") <- (
    Increment(1),
    Increment(2),
    Ctr(2)
)

Collecting facts from a computation

-dynamic(Result/1)

CollectEvens(LIST) <- (
    in_(X, LIST),
    X % 2 == 0,
    assertz(Result(X)),
    False
)
CollectEvens(_),

(Prefer findall for this pattern — it is cleaner and does not require dynamic predicates.)


Gotchas

  • Must declare -dynamic — without it, assertz/retract raise a permission error. This is intentional: it prevents accidental modification of predicates that should be stable.
  • assertz adds facts, not rulesassertz(foo(X) <- bar(X)) is not supported. Only ground or partially-ground facts can be asserted.
  • retract removes one clause — it removes the first matching clause only. Call it in a loop (or use findall + multiple retracts) to remove all matches.
  • Order mattersassertz appends, asserta prepends. The clause order affects which solution is found first.
  • Tabling interaction — if a tabled predicate depends on dynamic facts, remember to abolish_table after modifying the facts, or the cached answers will be stale.

See also: Directives-dynamic and other predicate directives, Tabling — automatic memoization with -table, Meta-Predicates — findall as an alternative to assert-based collection.