Prolog Coding Horror
This article dives into common 'coding horrors' in Prolog, detailing how seemingly innocuous programming choices can lead to defective code that loses solutions or is hard to reason about. It advocates for a 'pure' and declarative approach, demonstrating how modern Prolog features like CLP(FD) constraints offer a superior, more general way to write robust logic programs. The piece appeals to developers interested in functional and logic programming paradigms, offering practical advice for avoiding common pitfalls in a niche but powerful language.
The Lowdown
The article, 'Prolog Coding Horror' by Markus Triska, challenges Prolog programmers to critically examine their code for practices that, despite the language's unique strengths, can lead to subtle but significant defects. It highlights that the rebellious spirit often drawn to Prolog should be directed towards embracing its declarative power, rather than clinging to impure or outdated constructs. The author posits that a small set of rules, if followed, can prevent these 'horrors'.
- Losing Solutions: Programs can fail to report intended solutions when using impure and non-monotonic constructs like
!/0(cut),(->)/2(if-then-else), andvar/1. The recommended declarative alternatives include clean data structures, constraints likedif/2, and meta-predicates such asif_/3. - Global State: Modifying the global database with predicates like
assertz/1andretract/1introduces implicit dependencies, making code unpredictable and hard to maintain. The declarative solution involves threading state through predicate arguments or using semicontext notation. - Impure Output: Printing solutions directly to the terminal (
format/2) rather than letting the toplevel report them hinders reasoning, testing, and prevents the code from acting as a true relation. The article suggests describing output declaratively, potentially usingformat_//2for special formatting, to maintain testability and generality. - Low-level Language Constructs: Sticking to older, low-level arithmetic predicates such as
(is)/2,(=:=)/2, and(>)/2makes Prolog harder to teach and learn by forcing simultaneous understanding of declarative and operational semantics. The author strongly advocates for teaching and using modern CLP(FD) constraints instead.
To exemplify these issues, the author presents a 'horror factorial' implementation using !/0 and is/2, which fails to produce all solutions for general queries. The 'way out' is demonstrated by incrementally refactoring the factorial code: first by replacing low-level arithmetic with CLP(FD) constraints (#>/2, #=/2), and then by removing the cut (!/0). This transformation results in a pure, declarative n_factorial predicate that correctly handles the most general query, showcasing how a few simple changes yield a far more general and robust logic program.
In conclusion, the article urges Prolog programmers to rebel wisely: abandoning outdated, impure features in favor of declarative constructs. By embracing purity and modern features, programmers can write more general, maintainable, and acceptably performant Prolog code.