The Dynamic Database — assert & retract
Every predicate this course has used so far — from Course 1's parent/2 facts through prolog2-1's findall queries — has been fixed: written once in the source file, unchanging for the life of the program. This chapter introduces a genuinely different capability — changing the fact database itself, while the program runs — and is honest about what that costs.
assertz/1 and asserta/1 — Adding Facts at Runtime
assertz/1 adds a new fact (or rule) to the end of the database for that predicate — as if it had been written in the source file all along, but happening live, mid-query. asserta/1 does the same thing at the beginning instead, so it's tried first on the next query. Neither existed anywhere in this course's vocabulary before now — every predicate up to this point was defined once, statically, and stayed that way.
retract/1 — Removing Facts at Runtime
retract/1 removes the first fact matching its argument from the database. Combined, assertz and retract give a predicate the ability to update a fact — retract the old value, assert the new one — something no purely declarative set of facts and rules can do to itself.
A Practical Use — a Runtime Counter
The clearest demonstration of what this actually buys: a predicate that remembers how many times it's been called, something genuinely impossible using only Course 1's tools.
counter(0) is retracted and a new counter(N1) asserted on every call to increment — real, persistent, mutable state living inside Prolog's own database, surviving across separate top-level queries the way a variable in an imperative language would.
assertz tries to add a fact for it. The :- dynamic(counter/1). directive at the top tells Prolog in advance that this predicate is allowed to be modified at runtime, even before any clause for it exists yet.
The Real Tension With Declarative Purity
prolog1-8's cut already cost this course one honest admission — that a predicate's search could depend on execution order. assert/retract goes further: it means the facts themselves can differ depending on when a query is run relative to other goals, not just how the search through them is pruned. A query against score/2 run before an assertz call can produce a genuinely different answer than the identical query run after it — the same textual query, two different truths, depending purely on timing. That is about as far from "a fixed set of relations, true regardless of when you ask" as Prolog gets.
This deserves the same honesty this site has given every other real language tradeoff — most recently haskell2-4's own acknowledgment that unsafePerformIO genuinely can break Haskell's IO-tracking guarantee, not just theoretically. assert/retract is Prolog's own version of that same kind of escape hatch: a real, practical, frequently-used tool that knowingly steps outside the paradigm's own core promise.
| Facts | Meaning over time | What Course 1 offered |
|---|---|---|
| Static (source file) | fixed — same truth regardless of when queried | everything through prolog1-8 |
| Dynamic (assert/retract) | mutable — truth depends on timing relative to other goals | nothing — genuinely new in Course 2 |
assertz and retract are ordinary goals that succeed once and commit immediately — they are not undone if Prolog later backtracks past them the way a variable binding would be. A goal that asserts a fact and then fails, forcing backtracking, leaves that fact in the database permanently. This is a real, quiet source of bugs: pure logic guarantees that redoing a goal changes nothing observable; a database mutation breaks that guarantee outright.
Coding Challenges
Declare a dynamic predicate seen/1, use assertz/1 to add three different values to it one at a time, then query seen(X) and show all three results returned via backtracking.
📄 View solutionWrite the counter/1 and increment predicates exactly as shown in the chapter, call increment five times, query counter(X), and explain in a comment why this behavior would have been impossible using only Course 1's tools.
📄 View solutionWrite a short comment explaining, with a concrete example query, why a fact added via assertz is NOT automatically removed if the goal that asserted it is later backtracked into and fails — contrasting this with how an ordinary variable binding behaves on backtracking.
📄 View solutionChapter 2 Quick Reference
- assertz(Fact) — adds Fact to the end of the database for its predicate
- asserta(Fact) — adds Fact to the beginning, tried first on the next query
- retract(Fact) — removes the first matching fact from the database
- :- dynamic(Name/Arity). must be declared before assert/retract can target a predicate with no existing clauses
- A runtime counter (retract old value, assert new one) is genuinely impossible using only static facts and rules
- assert/retract mean the database's own truth can now depend on execution timing — a real cost to declarative purity, matching haskell2-4's own honest unsafePerformIO acknowledgment
- assert/retract are NOT undone on backtracking — a goal that asserts then fails leaves the assertion permanently in place