Prolog Coding Horror
196 points
• 1 day ago
• Article
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 中的"叛逆"应当朝向采用现代、声明式的特性,而不是固守过时和不纯的做法。通过拥抱纯性与约束,程序员既能写出更通用的逻辑程序,又能保持可接受的性能,从而充分发挥语言的关系性与声明式优势。
The article "Prolog Coding Horror" serves as a cautionary guide for Prolog programmers, warning against common anti-patterns that lead to defective code. It draws a parallel to Joseph Conrad's Heart of Darkness, framing these programming pitfalls as "horror" stories that undermine the elegance and power of logic programming. The core message is that while rebellion against industry norms can be valuable in Prolog, clinging to outdated or impure constructs is a misguided form of rebellion with high costs and no benefits.
The author identifies several major sources of horror in Prolog programming. The first is losing solutions by using impure and non-monotonic constructs like the cut operator (!/0), if-then-else (->)/2, and var/1. These can cause a program to fail to report intended solutions, which the author argues is worse than reporting wrong answers. The second horror is modifying the global database with predicates like assertz/1 and retract/1, which introduces implicit dependencies that make programs fragile and order-dependent. The third is impure output, where programmers print results directly to the terminal instead of letting the Prolog toplevel report them, which prevents reasoning about the output and breaks the relational nature of the code.
A particularly illustrative example is the "horror factorial" implementation. The initial version uses a cut and low-level arithmetic, causing it to lose solutions when queried generally. Even removing the cut leaves problems due to the use of (is)/2, which requires instantiated variables. The article demonstrates how moving to declarative constructs like CLP(FD) constraints (#>/2, #=/2) and removing the cut creates a more general program that works correctly for the most general query.
The author strongly advocates for staying within the pure, monotonic subset of Prolog. This means using constraints instead of low-level arithmetic, threading state through predicate arguments or semicontext notation instead of global variables, and describing solutions declaratively rather than printing them directly. The article emphasizes that low-level constructs make Prolog harder to teach and learn because they force students to grapple with both declarative and operational semantics simultaneously.
The conclusion reinforces that rebellion in Prolog should be directed toward using modern, declarative features rather than clinging to outdated ones. By embracing purity and constraints, programmers can create more general logic programs while maintaining acceptable performance. The article positions this approach as the path to writing great Prolog code that fully leverages the language's relational nature and declarative power.
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 和现代策略引擎延续。尽管在生产环境中直接使用较少,其声明式范式在配置管理、测试和静态分析等领域能带来清晰和简洁。社区既重视纯粹性,也重视实用性,认识到不纯特性虽然可能削弱声明式的优雅,但在实际可用性和性能方面有时必不可少。 • Prolog excels in domains involving complex rule-based reasoning, such as cloud resource management, where it can efficiently detect misconfigurations or unused assets using simple, generic rules and tabling for performance.
• It is highly effective for generating test scenarios in systems with many possible action sequences, allowing concise expression of constraints that would be cumbersome in procedural languages.
• Prolog serves as a strong target for LLM-generated code in planning, optimization, and diagnostics due to its high-level, declarative nature and historical roots in natural language processing.
• Real-world applications include workforce scheduling tools for non-programmers, telecom protocol testing, and policy evaluation systems like OpenPolicyagent, which uses a Prolog-like language (Datalog).
• Datalog, a subset of Prolog, is widely used in modern systems for static analysis (e.g., Rust's Polonius borrow checker), authorization (e.g., Biscuit), incremental computation, and graph databases, offering SQL-like recursion with logical inference.
• While Prolog itself is rarely seen in production, its conceptual descendants like Datalog and OPA are common, and Prolog derivatives such as Ciao or Mercury offer practical, performant alternatives for general-purpose logic programming.
• Early Erlang was prototyped in Prolog and shares syntactic similarities, but the two differ fundamentally: Prolog is declarative and backward-chaining, while Erlang is functional and concurrent; their paradigms are distinct despite surface-level resemblance.
• The four-port model (call, exit, redo, fail) is key to understanding Prolog's execution flow, emphasizing search-space navigation and pruning through backtracking.
• Impure features like assertz/1 or the cut (!/0) are sometimes necessary for efficiency or practicality, though experts advocate for pure, declarative styles using constraint logic programming (CLP) when possible.
• Markus Triska's work promotes disciplined, pure Prolog usage, but real-world use often requires a hybrid approach—using impure constructs for I/O or setup while keeping the core logic declarative and constraint-based.
Prolog remains a niche but powerful tool for problems involving complex constraints, rule-based inference, and combinatorial search, with its influence living on through Datalog and modern policy engines. While rarely used directly in production today, its declarative paradigm offers clarity and conciseness in domains like configuration management, testing, and static analysis. The community values both purity and pragmatism, recognizing that while impure features can undermine declarative elegance, they are sometimes essential for real-world usability and performance.