Prolog (SWI 9.4)

This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].

1 Assessment

This page assesses Prolog in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.

Core coupling 55/100 · Ecosystem coupling 45/100 · Engineering readiness 53/100

1.1 Core coupling

1.1.1 Type

  • ❌ There is no static type check (T1): A term’s type is its functor and its arity, known only at run time, so a type fault appears when the goal executes rather than when it is compiled
  • ❌ There are no sum types (T4): An alternative is a separate clause or an atom such as ok and error(...), and nothing checks that the cases are exhaustive
  • ❌ There are no generics (T5): Every predicate is polymorphic over any term by construction, so there is no declared type parameter that a definition could be reused over
  • ❌ There is no namespace and no module boundary (T6, T11): The standard gives one global predicate space, so two files that define foo/2 collide, and the module system that SWI-Prolog provides is a non-standard extension
  • ✅ Unification matches in any direction: append(X, Y, [1, 2, 3]) enumerates every split, so a relation is not fixed to one input and one output direction
  • ✅ The clause head is the pattern match: len([], 0). states a case directly in the head, so matching and dispatch are the same mechanism

1.1.2 Construction

  • ❌ Global mutable state is the default (C1): The clause database is one global store, and assertz/1, retract/1, and set_prolog_flag/2 change it from anywhere in the program
  • ❌ There is no immutability by default (C2): A term is immutable once built, but the state the program reads — clauses, flags, and the global variables of b_setval/2 — is mutable without a declared owner
  • ✅ A compound term is built by a functor and its arguments: point(1, 2) builds a term, and no constructor declaration must be kept in step with it
  • ✅ A clause is a term and can be constructed at run time: assertz(Head :- Body) takes an ordinary term, so a program and its data share one form
  • ⚠️ A clause added at run time depends on the database’s state: A program that asserts or retracts clauses couples its result to the clauses that are present and to the order they are in
  • ⚠️ Global variables are not logical variables: b_setval/2 stores a value that survives backtracking, so a part of the state lives outside the logical model

1.1.3 Memory

  • ✅ Backtracking reclaims automatically: When a goal fails, the bindings it made are undone and the space they held is reclaimed, so a search loop does not leak per-attempt state
  • ✅ The system collects the heap and the stacks: garbage_collect/0 and the atom-space controls are system predicates, so a program never frees a term itself
  • ❌ Collection is non-deterministic and stack-based (M2): Reclamation is the system’s decision, made through the trail, through stack unwinding, and through a garbage collection that the program cannot schedule
  • ❌ There is no RAII (M3): There is no scope-bound destructor, so an open stream, a socket, or a temporary file is released by an explicit close/1, and an exception can skip it
  • ❌ There is no ownership model (M4): No static lifetime or aliasing rule ties a term to an owner, so a shared structure is freed by the trail discipline rather than by a declaration
  • ⚠️ Deep recursion grows the stacks: Recursion that is not a tail call consumes stack and heap, and the bound is a system limit rather than a language rule

1.1.4 Error

  • ✅ throw/1 and catch/3 give a structured path: catch(Goal, Ball, Recovery) receives the thrown term and selects the recovery by matching it, so the handler is chosen by the error’s shape
  • ✅ The standard error term is a structured value: error(existence_error(procedure, P), Context) and the type_error, domain_error, instantiation_error, and permission_error forms are declared in ISO/IEC 13211-1, so a handler matches a pattern and not a message string
  • ✅ Failure stays distinct from an error: A goal that fails is not an error, and catch/3 does not intercept a failure, so the two outcomes remain separate
  • ✅ A runaway goal can be bounded: call_with_depth_limit/3 and call_with_inference_limit/3 are SWI extensions rather than ISO predicates, so a non-terminating search can be bounded and reported
  • ⚠️ A thrown ball of another shape is unchecked: A predicate may throw any term, and no declaration lists the throws it can make
  • ⚠️ A catch-all handler hides the cause: catch(Goal, _, fail) turns every failure reason into a plain failure, so a bug looks like a false result

1.1.5 Concurrency

  • ✅ SWI-Prolog provides threads and message queues: thread_create/3, thread_send_message/2, and thread_get_message/1 pass terms between threads without shared memory
  • ✅ An engine is an isolated computation: engine_create/3 and engine_next/2 (SWI) run a goal as an independent engine, so two computations interleave without sharing a stack
  • ✅ Message passing is the idiomatic design: A worker thread that receives a goal on a queue and answers on another queue couples the two sides to a term shape rather than to a lock
  • ✅ Constraint propagation needs no thread: library(clpfd) and library(clpz) propagate constraints inside one engine, so a constraint problem is solved by search rather than by parallelism
  • ⚠️ The ISO standard defines no concurrency: The core standard has no thread or task, so every concurrency mechanism comes from one implementation
  • ⚠️ Threads share the clause database: A mutation (assertz/1) or a global variable seen from two threads needs a lock such as with_mutex/2 or an explicit message protocol, and the system reports no race

1.1.6 Platform

  • ✅ The ISO standard is the reference: ISO/IEC 13211-1 fixes the syntax and the built-ins, and parts 2 and 3 add modules and Definite Clause Grammars, so code written to the core runs on several systems
  • ✅ Several independent implementations exist: SWI-Prolog, SICStus Prolog, GNU Prolog, YAP, Trealla, and Scryer Prolog implement the standard, and no single vendor owns the language
  • ✅ The system embeds in a host program: libswipl and libsicstus expose Prolog as a library, so a host language can call a predicate directly
  • ✅ The standard term syntax is a portable text form: write_canonical/1 and read/1 write and read a term in the standard syntax
  • ⚠️ The standard covers the core, not the libraries: library(clpfd), library(lists), library(readutil), and the HTTP server are one system’s, so a program that uses them is bound to that system
  • ⚠️ Behaviour outside the core differs: The ordering of assert and retract, tabling, and the exact error terms vary between systems, so portability means staying inside the core

1.1.7 Trade-offs (not coupling)

  • The declarative reading is the premise of the language: Writing what holds rather than how to find it is the design decision, and the loss of an explicit loop follows from it
  • The cut is a deliberate control tool: ! commits to the current clause, and the order-coupling it creates is the price of pruning the search rather than a forced property of every program
  • Clause order expresses a preference: Putting a base case first is a style that terminates a recursion, so order matters to the search and not to the specification
  • is/2 evaluates arithmetic eagerly: X is Y + 1 needs Y to be bound, which is a documented direction requirement rather than a coupling defect
  • Negation as failure is a semantics choice: \+ Goal succeeds when Goal fails, and the language documents the resulting restriction rather than hiding it
  • Prolog is not a general-purpose runtime: The assumption of a logic database and a search is a scope decision, not a coupling defect

1.2 Ecosystem coupling

1.2.1 Performance

  • ❌ The core resolves goals over a clause database (R1): A call is a search over the clauses through the engine’s instruction set, so a tight arithmetic or string loop trails an ahead-of-time compiled language
  • ✅ Indexing prunes the candidate clauses: First-argument indexing, and deep indexing in SWI-Prolog, selects the clauses that can match, so a well-keyed predicate does not scan the whole definition
  • ✅ A last call runs in constant space: Tail-call optimisation executes a final goal in place, so an iterative predicate does not grow the stack
  • ✅ Tabling avoids repeated subcomputations: :- table p/1 (XSB, SWI) memoises the answers of a relation, so a dynamic-programming problem is computed once
  • ⚠️ The cost model is the search: Unification, backtracking, and choice points are the units of work, so performance depends on how the problem was written as a search
  • ⚠️ A compiler is separate from the engine: compile/1 in SICStus and the JIT and optimised compiler in SWI change the measurement, so a benchmark states which mode and which system it used

1.2.2 Ecosystem

  • ❌ There is no package manager across the systems (Ec1): The standard defines no distribution, and each system ships its own mechanism, so a dependency is shaped by the system that provides it
  • ❌ The ecosystem is small (Ec3): The library set beyond the core, lists, apply, readutil, and the constraint libraries, is thin next to a mainstream language
  • ✅ SWI-Prolog ships a large standard library: clpfd, clpq, assoc, sgml, http/http_open, prolog_pack, and pengines come with one installation, so common work needs no external package
  • ✅ The systems are long-lived and maintained: SWI-Prolog has been developed since 1987, and SICStus, GNU Prolog, and YAP have decades of releases
  • ✅ The main manual is thorough: The SWI-Prolog manual documents each built-in with its mode, its determinism, and its errors
  • ⚠️ The community is small: Questions are answered, but by fewer people than in a mainstream language, so an unusual problem takes longer to resolve

1.2.3 Evolution

  • ✅ The ISO standard fixes the core: ISO/IEC 13211-1 (1995) pins the syntax and the built-ins, so core code does not move under a new release
  • ✅ SWI-Prolog stays backward compatible: swipl runs old code, offers --traditional for a strict ISO mode, and adds features as extensions
  • ✅ The established systems are stable: A program written for SICStus, GNU Prolog, or YAP years ago still runs on the current release
  • ✅ A program can be confined to the standard: write_canonical/1, read/1, and the standard control constructs give a form that moves between systems
  • ⚠️ The standard is not revised quickly: The core has seen no substantial revision since 1995, so a needed feature is added as an extension rather than to the standard
  • ⚠️ Extensions do not move between systems: library(clpfd) and library(assoc) differ in name and in detail, so a program that uses them is fixed to one system

1.2.4 Domain

  • ❌ The domain and the implementation are one choice (D1): The fit is logic programming over a clause database, and the system chosen fixes the flags, the library names, and the assert semantics
  • ❌ It is a poor fit for general-purpose work (D2): File handling, text processing, and numeric computation read as additions to a search language, so a general application belongs in another language
  • ✅ Excellent fit for a rule engine or an expert system: The facts and the rules are the program, and the inference engine is the language itself
  • ✅ Constraint solving is a strength: library(clpfd) solves finite-domain constraints, so a scheduling or puzzle problem is a few clauses rather than an algorithm
  • ✅ Natural-language processing is a traditional strength: A Definite Clause Grammar (-->) states a grammar production directly, and ISO/IEC 13211-3 standardises the form
  • ⚠️ A service application needs a library on top: A web endpoint depends on library(http/http_server), which is one system’s addition to the core

1.2.5 Learning

  • ❌ Declarative thinking is a different model (L1): A beginner must write relations instead of steps, and the change of mental model, not the syntax, is the hard part
  • ❌ The search must be understood to debug a program: The order of the clauses, the trail, and the cut decide how a program behaves, and a wrong order is a common beginner fault
  • ⚠️ Unification has its own rules: The treatment of the occurs check, the anonymous variable _, and the difference between = and == must be learned before a program can be read
  • ✅ The core language is small: A program is facts, rules, and a query, and the constructs beyond :-, ,, ;, and ! are few
  • ✅ The classic textbooks are excellent: The standard references, including Clocksin and Mellish, Sterling and Shapiro, and O’Keefe’s The Craft of Prolog, remain current

1.2.6 Syntax

  • ✅ Terms, clauses, and queries share one form: A fact and a rule differ only by the :- body, so a reader learns one shape
  • ✅ A Definite Clause Grammar reads as a production: sentence --> noun_phrase, verb_phrase. states the rule directly
  • ✅ The operator table can be extended: :- op(700, xfx, ===) declares a notation, so a domain can add an operator such as -->
  • ✅ A clause ends with a full stop: The . terminates a clause and the term syntax is uniform across the file
  • ⚠️ The comma has two roles: f(a, b) separates arguments and (a, b) conjoins goals, so the context decides the meaning of one character
  • ⚠️ Operator precedence must be tracked: An expression such as a ; b, c parses by the precedence table, so a missing pair of brackets changes the meaning

1.2.7 Testing

  • ❌ A type fault surfaces only at run time (G3): No static check rejects a goal, so a wrong argument, an unbound variable in an arithmetic goal, or a misspelled predicate fails during the run
  • ✅ plunit ships with SWI-Prolog: :- begin_tests(...) and :- end_tests(...) declare tests, and run_tests/0 runs the suite
  • ✅ Property-based testing is available: library(quickcheck) generates terms from a type skeleton and reports a minimal counterexample
  • ✅ The tracer steps through the search: trace/0 and gtrace/0 show the call, exit, redo, and fail ports of every goal, so a search can be watched one step at a time
  • ✅ A query is itself a test: A goal that must succeed and a goal that must fail are statements about the relation, so the test needs no fixture
  • ⚠️ A left choice point is a fault a success hides: A predicate that should be deterministic can leave an alternative, so a test must assert det or semidet to catch it

1.2.8 Security

  • ❌ Dynamic typing weakens a trust boundary (Q2): A term carries no declared type, so a value from an untrusted source is separated from a goal only by an explicit check, and the check is the programmer’s
  • ✅ Unification binds symbols, not memory: There is no pointer arithmetic and no buffer in the language, so a buffer overflow cannot be written in Prolog source
  • ✅ catch/3 isolates a failure: A goal that throws is caught at a known point, so a fault need not end the process
  • ✅ The core has no text interpreter: ISO Prolog does not evaluate a string; a term is built from a functor and its arguments, so text becomes control only where the program asks for it
  • ⚠️ call/1 on a term built from input is the injection pattern: call(term_to_atom(T, Input)) executes the goal that the input describes
  • ⚠️ The atom space is finite: A program that makes an atom from every input, through atom_concat/3 or atom_length/2, can exhaust the atom space of the system

1.3 Engineering readiness

1.3.1 Reproducibility

  • ❌ Builds are not byte-reproducible by default.
  • ❌ Dependencies resolve without a lock by default.

1.3.2 Supply chain

  • ⚠️ Published packages carry no signed provenance by default.
  • ⚠️ The package registry is a flat name space.

1.3.3 Observability

  • ⚠️ No stable tracing and metrics standard.
  • ⚠️ The standard library has no structured logging.

1.3.4 Feedback

  • ⚠️ No fast in-editor analysis.

1.3.5 Interop

  • ⚠️ No maintained portable target.

1.3.6 Governance

  • ✅ No charged mechanism in this dimension.

1.4 Profile

Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.

1.4.1 Energy and cost

What this axis asks: what does a running program cost in energy and time?

  • ❌ Interpreted by default: the slowest class in the energy and time benchmarks [5]; a causal re-analysis attributes most of the gap to execution time [6].

1.4.2 AI and agent readiness

What this axis asks: how well does a coding model and an agent work in this language?

  • ❌ Sparse training data: models write it unreliably [7].

1.4.3 Safety certification

What this axis asks: is there a certified or standards-qualified path to a safety-critical system?

  • ⚠️ No certified or standards-qualified path for safety-critical systems.

1.5 Scoring detail

1.5.1 Core coupling Dimensions

Dimension Mechanism Description Present Cost
Type T1 dynamic no static check ✅ −15
T2 weak static — 0
T3 dynamic/duck dispatch — 0
T4 no sum types ✅ −4
T5 no generics ✅ −4
T6 no namespaces ✅ −8
T7 type erasure/reflection — 0
T8 operator overloading — 0
T9 inheritance required for polymorphism — 0
T10 no null-safety — 0
T11 no module boundary ✅ −8
T12 no effect tracking ✅ −8
T13 async/sync colouring — 0
T14 unchecked foreign boundary ✅ −4
Type subtotal −15 (−51)
Construction C1 global mutable state by default ✅ −15
C2 no immutability by default ✅ −4
C3 constructors coupled to allocation — 0
C4 no constructor/validation — 0
Construction subtotal −15 (−19)
Memory M1 manual deallocation — 0
M2 GC non-deterministic pauses ✅ −8
M3 no RAII ✅ −4
M4 no ownership model ✅ −8
Memory subtotal −15 (−20)
Error Er1 no structured path — 0
Er2 unchecked exceptions only — 0
Er3 errors ignored/non-exhaustive — 0
Er4 no composable result type — 0
Error subtotal 0
Concurrency K1 GIL/no parallel — 0
K2 shared mutable state default — 0
K3 manual sync no race detection — 0
K4 no structured concurrency — 0
K5 no primitives — 0
Concurrency subtotal 0
Platform P1 cannot function outside one vendor/domain — 0
P2 single-vendor but other uses — 0
P3 legacy/discontinued platform — 0
Platform subtotal 0

Core Dimensions score: 55/100 — total cost = −45 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.2 Ecosystem coupling Dimensions

Dimension Mechanism Description Present Cost
Performance R1 interpreted no AOT/JIT ✅ −8
R2 GC pauses — 0
R3 JIT warmup — 0
Performance subtotal −8
Ecosystem Ec1 no package manager ✅ −4
Ec2 fragmented tooling — 0
Ec3 small/niche/discontinued ✅ −8
Ec4 proprietary — 0
Ec5 platform-locked tooling — 0
Ec6 monoculture — 0
Ec7 legacy burden — 0
Ecosystem subtotal −12
Evolution V1 breaking changes — 0
V2 frozen — 0
V3 discontinued — 0
V4 glacial standards — 0
Evolution subtotal 0
Domain D1 domain/vendor lock-in ✅ −15
D2 poor domain fit ✅ −8
D3 framework forces architecture — 0
Domain subtotal −15 (−23)
Learning L1 steep ✅ −8
L2 large surface/multi-paradigm — 0
L3 sparse docs — 0
Learning subtotal −8
Syntax S1 verbose — 0
S2 inconsistent — 0
S3 punctuation-heavy — 0
Syntax subtotal 0
Testing G1 weak tooling — 0
G2 poor debugging — 0
G3 runtime-only errors ✅ −4
Testing subtotal −4
Security Q1 memory-unsafe — 0
Q2 dynamic/weak typing risk ✅ −8
Q3 known vulnerability/injection — 0
Security subtotal −8

Meta Dimensions score: 45/100 — total cost = −55 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.3 Engineering readiness Dimensions

Dimension Mechanism Description Present Cost
Reproducibility Y1 build not reproducible ✅ −8
Y2 dependencies not pinned ✅ −8
Reproducibility subtotal −15 (−16)
Supply chain Y3 no signed provenance ✅ −8
Y4 registry without namespacing ✅ −4
Supply chain subtotal −12
Observability Y5 no stable tracing standard ✅ −8
Y6 no structured logging ✅ −4
Observability subtotal −12
Feedback Y7 slow build — 0
Y8 no fast in-editor analysis ✅ −4
Feedback subtotal −4
Interop Y9 no foreign-function interface — 0
Y10 no portable target ✅ −4
Interop subtotal −4
Governance Y11 single-vendor governance — 0
Y12 closed specification — 0
Governance subtotal 0

Readiness Dimensions score: 53/100 — total cost = −47 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

Verdict: Prolog’s design yields a mixed profile: a moderate core coupling score (55/100) and a low platform and ecosystem score (45/100). One core mechanism is genuinely decoupling, because unification matches a pattern in any direction and so a relation is not fixed to one input and one output. Everything else is the price of the paradigm: there is no static type, no sum type, no generic, and no namespace or module boundary, the clause database is global mutable state, the search couples a result to clause order, collection follows the trail rather than the program, and the domain fit is narrow. The surrounding platform adds the rest, since the core resolves goals over a clause database, the standard has not moved since 1995, the ecosystem is small, and whichever system is chosen fixes the libraries.


The formal development. The definitions, theorems, and proofs behind this page are published in:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
ISO/IEC JTC 1/SC 22, “Programming languages — Prolog.” International Organization for Standardization; International Standard ISO/IEC 13211-1, Geneva, Switzerland, 1995.
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[7]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.