Prolog 数据库:assert 与 retract
在 Prolog 中,程序不仅是规则和事实的集合,还可以在运行时动态修改自身。这种能力通过两个核心谓词实现:assert 和 retract。它们允许你在程序执行过程中添加或删除事实和规则,从而构建一个可变的知识库。本文将手把手教你如何正确使用这两个操作。
理解动态数据库
Prolog 默认将所有子句(事实和规则)存储在静态数据库中——这意味着你不能在运行时更改它们。要启用动态修改,必须先声明某个谓词为“动态”。
声明动态谓词的方法是在代码顶部使用 :- dynamic/1 指令。例如:
:- dynamic likes/2.
这行代码告诉 Prolog:“likes/2 这个谓词(接受两个参数)的内容可以在运行时被修改。”如果不做此声明,尝试用 assert 添加 likes(X, Y) 会失败并报错。
使用 assert 添加事实或规则
assert/1 的作用是将一个新的子句插入到数据库中。它有两种常用形式:
assert(子句):将子句添加到数据库末尾。asserta(子句):将子句添加到数据库开头(“a” 表示 at front)。assertz(子句):等同于assert/1,明确表示添加到末尾(“z” 表示 at end)。
步骤:添加一个事实
假设你想记录“小明喜欢苹果”,可以这样做:
-
确保谓词已声明为动态:
:- dynamic likes/2. -
执行查询添加事实:
?- assert(likes(xiaoming, apple)). -
验证是否成功:
?- likes(xiaoming, apple). true.
此时,likes(xiaoming, apple) 已成为程序的一部分,后续查询可以直接使用。
步骤:添加一条规则
你也可以动态添加规则。例如,定义“如果 A 喜欢 B,且 B 是水果,则 A 吃 B”:
-
声明涉及的谓词为动态(包括新规则中的
eats/2)::- dynamic likes/2, eats/2, fruit/1. -
添加规则:
?- assert((eats(Person, Fruit) :- likes(Person, Fruit), fruit(Fruit))).注意:规则必须用额外的一对括号包裹,否则语法错误。即写成
(Head :- Body)而非Head :- Body。 -
添加辅助事实:
?- assert(fruit(apple)). ?- assert(likes(xiaoming, apple)). -
查询结果:
?- eats(xiaoming, apple). true.
使用 retract 删除子句
retract/1 用于从数据库中移除一个匹配的子句。它只会删除第一个匹配项;若需删除全部,应使用 retractall/1。
步骤:删除一个具体事实
继续上面的例子:
-
确认当前存在该事实:
?- likes(xiaoming, apple). true. -
执行删除:
?- retract(likes(xiaoming, apple)). true. -
再次查询验证是否已删除:
?- likes(xiaoming, apple). false.
步骤:删除所有匹配的事实
如果你想清除某人所有的喜好记录:
-
先添加多个事实:
?- assert(likes(xiaoming, apple)). ?- assert(likes(xiaoming, banana)). -
使用
retractall/1一次性删除:?- retractall(likes(xiaoming, _)).这里的
_是匿名变量,表示“任意值”。该命令会删除所有likes(xiaoming, X)形式的事实。 -
验证结果:
?- likes(xiaoming, X). false.
常见陷阱与注意事项
以下问题极易导致程序行为异常,务必注意:
-
未声明动态谓词:直接对静态谓词使用
assert或retract会失败。始终在文件开头用:- dynamic(...).声明。 -
规则格式错误:添加规则时忘记外层括号。正确写法是
assert((Head :- Body)),不是assert(Head :- Body)。 -
变量作用域误解:
assert(likes(X, apple))中的X是一个逻辑变量,实际存入的是自由变量(类似占位符),而非具体值。通常应使用具体原子(如xiaoming)或在绑定后 assert。 -
删除不彻底:
retract/1只删一个匹配项。若数据库中有重复事实,需多次调用或改用retractall/1。 -
性能影响:频繁使用
assert/retract会使程序变慢,因为每次查询都可能遍历动态数据库。仅在必要时使用。
实战示例:简易记忆系统
下面是一个完整的小例子,展示如何用 assert 和 retract 构建一个能“记住”和“遗忘”的系统。
:- dynamic remembered/2.
remember(Person, Fact) :-
assert(remembered(Person, Fact)).
forget(Person, Fact) :-
retract(remembered(Person, Fact)).
knows(Person, Fact) :-
remembered(Person, Fact).
使用方式:
?- remember(alice, 'Paris is the capital of France').
true.
?- knows(alice, X).
X = 'Paris is the capital of France'.
?- forget(alice, 'Paris is the capital of France').
true.
?- knows(alice, X).
false.
这个系统清晰展示了动态数据库的核心用途:在运行时持久化信息,并按需清除。
assert 与 retract 的语义细节
下表总结了关键谓词的行为差异:
| 谓词 | 作用 | 是否删除所有匹配项 | 典型用途 |
|---|---|---|---|
assert/1 或 assertz/1 |
在数据库末尾添加子句 | 否 | 追加新知识 |
asserta/1 |
在数据库开头添加子句 | 否 | 优先级高的规则 |
retract/1 |
删除第一个匹配的子句 | 否 | 精确移除 |
retractall/1 |
删除所有匹配的子句 | 是 | 批量清理 |
注意:asserta 和 assertz 影响子句的搜索顺序。Prolog 从上到下尝试子句,因此 asserta 添加的规则会比原有规则更早被尝试。
安全使用建议
- 隔离动态数据:尽量将动态谓词集中管理,避免与核心逻辑混杂。
- 避免全局副作用:在多线程或复杂回溯场景中,动态修改可能导致状态混乱。如需可靠状态,考虑使用
nb_setval/nb_getval等非回溯变量。 - 测试回溯行为:在失败驱动的循环中,
assert的效果可能因回溯而撤销。若需永久生效,应在无回溯的上下文中调用(如用once/1包裹)。
?- once((read(X), assert(temp(X)))).
这样即使后续失败,temp(X) 仍保留在数据库中。
启动你的 Prolog 环境,输入上述代码,动手尝试添加和删除事实。你会发现,动态数据库虽小,却为逻辑编程打开了状态管理的大门。

暂无评论,快来抢沙发吧!