Prolog Coding Horror
196 points • 1 day agoArticle Link

文章 "Prolog Coding Horror" 向 Prolog 程序员发出警示,指出那些会导致代码有缺陷的常见反模式。作者借用了 Joseph Conrad 的 Heart of Darkness 作比,把这些编程陷阱描述为"恐怖"故事——它们破坏了逻辑编程的优雅与表达力。文章的核心观点是:在 Prolog 中对既有规范保持质疑是有价值的,但执着于过时或不纯的构造只是一种代价高昂而无益的叛逆。

作者列举了几类主要的"恐怖"来源。第一类是使用不纯且非单调的构造,比如 cut (!/0) 、 if-then-else (->)/2 和 var/1,这些会导致程序遗漏本应返回的解——作者认为这比返回错误结果更糟。第二类是通过 assertz/1 、 retract/1 等谓词修改全局数据库,这会引入隐式依赖,使程序脆弱并依赖执行顺序。第三类是不纯的输出:程序员直接把结果打印到终端,而不是让 Prolog 的顶层(toplevel)来报告结果,这既妨碍对输出的推理,也破坏了代码的关系性。

一个说明性例子是所谓的 "horror factorial" 实现。初始版本用了 cut 和低级算术,导致在通用查询下会丢失解。即便去掉 cut,仍因使用 is/2(要求变量已实例化)而存在问题。文章展示了如何用声明性手段改写:用 CLP(FD) 约束(#>/2 、 #=/2)替代低级算术,并去掉 cut,从而得到在最一般查询下也能正确工作的更通用程序。

作者强烈主张尽量停留在 Prolog 的纯、单调子集内。具体做法包括:用约束替代底层算术;通过谓词参数或半上下文记法来传递状态,而不是依赖全局变量;以声明式方式描述解,而不是直接打印。文章指出,低级构造还会增加教学难度,因为学生不得不同时面对声明式语义和操作式语义。

结论是:在 Prolog 中的"叛逆"应当朝向采用现代、声明式的特性,而不是固守过时和不纯的做法。通过拥抱纯性与约束,程序员既能写出更通用的逻辑程序,又能保持可接受的性能,从而充分发挥语言的关系性与声明式优势。

83 comments • Comments Link

Prolog 在涉及复杂规则推理的领域表现出色,例如云资源管理,能利用简单通用的规则和表格化技术高效地发现配置错误或未使用的资产。

在动作序列繁多的系统中,Prolog 非常适合生成测试场景,能以简洁的方式表达约束,这些在过程式语言中往往显得繁琐。

由于其高级的声明式特性和在自然语言处理方面的历史渊源,Prolog 是 LLM 生成代码用于规划、优化和诊断时的理想目标语言。

实际应用包括面向非程序员的排班工具、电信协议测试以及策略评估系统,例如 OpenPolicyagent(使用类似 Prolog 的 Datalog 语言)。

Datalog 是 Prolog 的一个子集,在现代系统中被广泛用于静态分析(例如 Rust 的 Polonius 借用检查器)、授权(例如 Biscuit)、增量计算和图数据库,提供类似 SQL 的递归能力并结合逻辑推理。

虽然 Prolog 本身在生产环境中直接使用不多,但其概念性后代如 Datalog 和 OPA 非常常见,Prolog 的衍生语言如 Ciao 或 Mercury 则为通用逻辑编程提供了实用且高性能的替代方案。

早期的 Erlang 曾在 Prolog 中有过原型实现,二者在语法上有相似之处,但本质不同:Prolog 是声明式并采用后向链式推理,Erlang 则是函数式并支持并发——表面相似,范式却截然不同。

理解 Prolog 的执行流程关键在于"四端口模型"(call 、 exit 、 redo 、 fail),它强调通过回溯来遍历并剪枝搜索空间。

像 assertz/1 或 cut(!/0)这样的不纯特性有时为提高效率或实现实用功能所必需,尽管专家通常主张在可能时采用纯声明式风格,并结合约束逻辑编程(CLP)。

Markus Triska 的工作倡导有纪律的纯 Prolog 使用方式,但实际中通常采取混合方法:用不纯构造处理 I/O 或初始化,同时让核心逻辑保持声明式和基于约束的特性。

总体来看,Prolog 仍是一个小众但功能强大的工具,适合处理复杂约束、基于规则的推理和组合搜索问题,其影响通过 Datalog 和现代策略引擎延续。尽管在生产环境中直接使用较少,其声明式范式在配置管理、测试和静态分析等领域能带来清晰和简洁。社区既重视纯粹性,也重视实用性,认识到不纯特性虽然可能削弱声明式的优雅,但在实际可用性和性能方面有时必不可少。