Prolog

Definition

Prolog (from "PROgrammation en LOGique," French for "Programming in Logic") is a declarative logic programming language rooted in first-order predicate calculus. Unlike imperative languages that specify step-by-step instructions for computation, Prolog programs consist of facts (ground truths), rules (logical implications), and queries (goals to prove). The Prolog runtime engine automatically searches for solutions through unification -- a bidirectional pattern matching mechanism -- and backtracking -- systematic exploration of alternative proof paths when a derivation fails. First developed by Alain Colmerauer and Philippe Roussel at the University of Aix-Marseille in 1972, Prolog became a cornerstone of artificial intelligence research, powering expert systems, natural language understanding, theorem provers, and symbolic computation throughout the 1980s and beyond.

Prolog's fundamental insight is that programming can be reduced to specifying what is true rather than how to compute it. A Prolog program defines a logical database of relationships, and computation proceeds by attempting to prove queries against that database through resolution -- the inference mechanism that chains rules together to derive new facts from existing ones. This paradigm makes Prolog exceptionally well-suited for domains where the problem structure is naturally expressed as relationships and constraints rather than sequential transformations.

Overview

Prolog occupies a unique position in the landscape of programming paradigms. While functional languages like Elixir and Haskell abstract over computation as function application, and object-oriented languages abstract over entities and their behaviors, Prolog abstracts over logical truth. A Prolog program is, in essence, a set of axioms and inference rules that define a miniature logical theory. Running a program means asking the runtime to prove a theorem within that theory.

The language's execution model is built on three pillars:

PillarMechanismPurpose
FactsGround clauses (parent(tom, bob).)Declare known truths in the knowledge base
RulesHorn clauses (grandparent(X, Z) :- parent(X, Y), parent(Y, Z).)Define logical implications and derived relationships
QueriesGoals (?- grandparent(tom, Who).)Ask the system to find solutions satisfying constraints

The Prolog engine processes queries through SLD resolution (Selective Linear Definite clause resolution), attempting to unify the query with facts and rule heads, recursively solving sub-goals, and backtracking when a derivation path fails. This produces a search tree where each branch represents a possible proof, and solutions correspond to successful leaf nodes.

Prolog's influence extends far beyond programs written directly in the language. Its core concepts -- unification, backtracking, declarative knowledge bases, and Horn clause logic -- have been absorbed into database query languages (SQL's relational algebra shares theoretical roots), type inference algorithms (Hindley-Milner unification), pattern matching in functional languages (Elixir, Erlang, Haskell), constraint solvers, and modern AI reasoning systems. Understanding Prolog illuminates the theoretical foundations underlying much of modern computing.

Historical Context

The development of Prolog marked a pivotal moment in computer science history. Several key milestones shaped the language and its ecosystem:

  • 1965: J. Alan Robinson publishes the resolution principle, providing the theoretical foundation for automated theorem proving.
  • 1972: Alain Colmerauer and Philippe Roussel create the first Prolog implementation at the University of Aix-Marseille.
  • 1977: David H.D. Warren develops the Warren Abstract Machine (WAM), an efficient execution model that made Prolog practical for real-world use.
  • 1982: Japan's Fifth Generation Computer Systems (FGCS) project selects a concurrent Prolog variant as its core language, driving massive investment in logic programming research.
  • 1995: ISO/IEC 13211-1 standardizes Prolog, establishing a common specification across implementations.
  • 2000s-present: Constraint Logic Programming (CLP) extensions, probabilistic logic programming (ProbLog), and integration with modern AI systems keep the paradigm relevant.

Technical Details

Unification

Unification is Prolog's central computational mechanism. It generalizes pattern matching by working bidirectionally -- both the query and the database term can contain variables, and unification finds the most general substitution (if one exists) that makes the two terms identical.

The unification algorithm operates as follows:

  1. Atoms and numbers: Unify only with themselves. hello unifies with hello; 42 unifies with 42.
  2. Variables: Unify with any term, binding the variable to that term. X unifies with hello, binding X = hello.
  3. Compound terms: Unify if they have the same functor and arity, and all corresponding arguments unify recursively. f(X, b) unifies with f(a, Y), yielding X = a, Y = b.
  4. Occurs check: Prevents infinite terms. X should not unify with f(X) (though many Prolog implementations skip this check for performance).

When Prolog encounters a goal that can match multiple clauses, it tries them in order. If a subsequent sub-goal fails, Prolog backtracks to the most recent choice point and tries the next alternative. This depth-first search with chronological backtracking provides a complete exploration of the solution space (for finite domains).

Prolog-Inspired Patterns in Elixir

While Elixir is not a logic programming language, many Prolog concepts map directly to Elixir constructs. Pattern matching in function heads, guard clauses for constraint filtering, and recursive data traversal all echo Prolog's declarative approach.

defmodule PrismaticReasoning.KnowledgeBase do
  @moduledoc """
  A Prolog-inspired knowledge base and inference engine implemented
  in Elixir. Demonstrates how logic programming concepts translate
  to functional programming with pattern matching and recursion.
  """

  @type fact :: {atom(), list(term())}
  @type rule :: {atom(), list(term()), list({atom(), list(term())})}
  @type knowledge :: %{facts: list(fact()), rules: list(rule())}

  @spec new() :: knowledge()
  def new do
    %{facts: [], rules: []}
  end

  @spec assert_fact(knowledge(), atom(), list(term())) :: knowledge()
  def assert_fact(kb, predicate, args) do
    %{kb | facts: [{predicate, args} | kb.facts]}
  end

  @spec assert_rule(knowledge(), atom(), list(term()), list({atom(), list(term())})) :: knowledge()
  def assert_rule(kb, head_pred, head_args, body) do
    %{kb | rules: [{head_pred, head_args, body} | kb.rules]}
  end

  @spec query(knowledge(), atom(), list(term())) :: {:ok, list(map())} | {:error, :no_solution}
  def query(kb, predicate, args) do
    bindings = resolve(kb, [{predicate, args}], %{})

    case bindings do
      [] -> {:error, :no_solution}
      results -> {:ok, results}
    end
  end

  # SLD resolution: attempt to prove all goals in the goal list
  defp resolve(_kb, [], bindings), do: [bindings]

  defp resolve(kb, [{predicate, args} | rest_goals], bindings) do
    fact_results =
      kb.facts
      |> Enum.flat_map(fn {fact_pred, fact_args} ->
        case unify(predicate, args, fact_pred, fact_args, bindings) do
          {:ok, new_bindings} -> resolve(kb, rest_goals, new_bindings)
          :fail -> []
        end
      end)

    rule_results =
      kb.rules
      |> Enum.flat_map(fn {rule_pred, rule_args, body} ->
        case unify(predicate, args, rule_pred, rule_args, bindings) do
          {:ok, new_bindings} -> resolve(kb, body ++ rest_goals, new_bindings)
          :fail -> []
        end
      end)

    fact_results ++ rule_results
  end

  defp unify(pred, args, pred, fact_args, bindings) when length(args) == length(fact_args) do
    Enum.zip(args, fact_args)
    |> Enum.reduce_while({:ok, bindings}, fn {a, b}, {:ok, acc} ->
      case unify_terms(a, b, acc) do
        {:ok, new_acc} -> {:cont, {:ok, new_acc}}
        :fail -> {:halt, :fail}
      end
    end)
  end

  defp unify(_pred1, _args1, _pred2, _args2, _bindings), do: :fail

  defp unify_terms({:var, name}, term, bindings) do
    case Map.get(bindings, name) do
      nil -> {:ok, Map.put(bindings, name, term)}
      ^term -> {:ok, bindings}
      _other -> :fail
    end
  end

  defp unify_terms(term, {:var, name}, bindings), do: unify_terms({:var, name}, term, bindings)
  defp unify_terms(same, same, bindings), do: {:ok, bindings}
  defp unify_terms(_a, _b, _bindings), do: :fail
end

The Warren Abstract Machine (WAM)

The WAM, designed by David H.D. Warren in 1983, transformed Prolog from an academic curiosity into a practical programming tool. The WAM compiles Prolog clauses into instructions for an abstract register machine, achieving performance within an order of magnitude of imperative languages for many workloads. Key WAM innovations include:

ComponentPurposeImpact
Argument registersPass arguments without stack allocationReduced memory overhead
Choice pointsStore machine state at branch points for backtrackingEfficient systematic search
TrailRecord variable bindings for undo on backtrackCorrect backtracking semantics
HeapStore compound terms and variable bindingsDynamic term construction
Last call optimizationReuse stack frame for tail-recursive clausesConstant-space iteration
IndexingFirst-argument indexing for clause selectionAvoid unnecessary backtracking

Implementation

Building a Rule Engine

Logic programming principles translate naturally into rule engines -- systems that evaluate conditions against a knowledge base and trigger actions. In the Prismatic Platform, rule-based reasoning underpins agent decision-making, quality gate evaluation, and compliance assessment.

defmodule PrismaticReasoning.RuleEngine do
  @moduledoc """
  A forward-chaining rule engine inspired by Prolog's inference
  mechanism. Rules fire when their conditions match against the
  current fact base, producing new facts until a fixed point.
  """

  @type condition :: (MapSet.t() -> boolean())
  @type action :: (MapSet.t() -> MapSet.t())
  @type rule :: %{name: String.t(), condition: condition(), action: action()}

  @spec run(MapSet.t(), list(rule()), non_neg_integer()) :: MapSet.t()
  def run(facts, rules, max_iterations \\ 1000) do
    forward_chain(facts, rules, 0, max_iterations)
  end

  defp forward_chain(facts, _rules, iteration, max) when iteration >= max, do: facts

  defp forward_chain(facts, rules, iteration, max) do
    new_facts =
      rules
      |> Enum.reduce(facts, fn rule, acc ->
        if rule.condition.(acc), do: rule.action.(acc), else: acc
      end)

    if MapSet.equal?(new_facts, facts) do
      facts
    else
      forward_chain(new_facts, rules, iteration + 1, max)
    end
  end
end

Constraint Logic Programming

Constraint Logic Programming (CLP) extends Prolog with constraint domains -- finite domains (CLP(FD)), real numbers (CLP(R)), or Boolean variables (CLP(B)). Instead of enumerating solutions through generate-and-test, CLP prunes the search space by propagating constraints before enumeration. This dramatically reduces computation for scheduling, planning, and optimization problems.

The Prismatic Platform leverages CLP-inspired constraint propagation in its quality gate system, where multiple interdependent quality metrics must simultaneously satisfy thresholds before a commit is approved.

Comparison

Prolog vs. Other Paradigms

DimensionProlog (Logic)Elixir (Functional)Python (Imperative)Java (OOP)
Core abstractionLogical relationsFunctions and dataStatements and variablesObjects and methods
Execution modelProof searchReductionSequential executionMethod dispatch
StateLogical variables (single-assignment)Immutable bindingsMutable variablesMutable object fields
Control flowUnification + backtrackingPattern matching + recursionIf/else + loopsIf/else + loops
ConcurrencyLimited (implementation-dependent)BEAM processes (millions)GIL-limited threadsOS threads + locks
Primary strengthKnowledge representationFault-tolerant distributed systemsRapid prototypingEnterprise frameworks
SearchBuilt-in (automatic backtracking)Manual (explicit recursion)Manual (explicit loops)Manual (iterators)
TypingUntyped (terms)Dynamic (with optional specs)Dynamic (with optional hints)Static (nominal)

Prolog vs. Datalog

Datalog, a syntactic subset of Prolog, restricts the language to guarantee termination and enable efficient bottom-up evaluation. While Prolog allows arbitrary term construction (potentially creating infinite data structures), Datalog limits predicates to flat facts and stratified negation, making it suitable for database querying and static analysis. Many modern graph databases and policy engines use Datalog semantics rather than full Prolog.

Prolog vs. Answer Set Programming (ASP)

Answer Set Programming takes a different approach to logic programming, computing stable models (answer sets) of logic programs rather than using SLD resolution. ASP excels at combinatorial optimization and planning problems where all solutions are needed simultaneously, while Prolog's depth-first search is better suited for interactive querying and procedural reasoning.

Best Practices

When to Use Logic Programming Concepts

  1. Knowledge-intensive domains: When the problem is naturally expressed as relationships between entities (genealogy, ontologies, compliance rules, security policies).
  2. Search and optimization: When exploring a combinatorial space of configurations, schedules, or plans.
  3. Symbolic AI: When reasoning about structured knowledge rather than statistical patterns.
  4. Grammar and parsing: Definite Clause Grammars (DCGs) provide elegant parsing specifications.
  5. Validation and verification: When checking conformance of data structures against complex rule sets.

Design Principles for Logic-Based Systems

  • Separate knowledge from control: Define what is true independently from how it is queried. This separation enables the same knowledge base to answer different questions without modification.
  • Minimize cuts and side effects: The Prolog cut (!) and I/O operations break the pure logical semantics. Use them sparingly and document their necessity.
  • Use modes and types: Document expected argument instantiation patterns (input vs. output) even though Prolog does not enforce them syntactically.
  • Stratify negation: When using negation-as-failure (\+), ensure the negated predicate is fully defined before it is negated to avoid unsound reasoning.
  • Index on discriminating arguments: Place the most distinctive argument first to enable first-argument indexing in the WAM.

Pitfalls

Common Mistakes in Logic Programming

PitfallDescriptionMitigation
Infinite loopsLeft-recursive rules without base cases cause non-terminationAlways define base cases; use tabling/memoization where available
Occurs check omissionMost Prolog implementations skip the occurs check for performance, allowing unsound circular unificationsEnable occurs check for safety-critical applications
Cut abuseOveruse of ! destroys the logical reading of programs and prevents backtracking to valid solutionsPrefer green cuts (performance only); document all red cuts
Negation confusionNegation-as-failure is not classical logical negation; it differs when variables are unboundGround negative goals before evaluation; use well-founded semantics
Order dependenceClause order affects execution due to depth-first searchBe aware that reordering clauses changes behavior; test all orderings
Performance opacityAutomatic search can hide exponential blowup in seemingly simple programsProfile with trace/spy; add determinism checks; use indexing hints

Misapplying Prolog Paradigms

Logic programming is not a universal solution. Attempting to express inherently sequential, stateful computations (GUI event loops, real-time stream processing, low-level system programming) in pure Prolog results in awkward, inefficient code. The right approach is to use logic programming for the knowledge representation and reasoning layer while delegating imperative concerns to appropriate languages -- exactly the architecture the Prismatic Platform employs, with Elixir handling process management and concurrency while logic-inspired patterns handle validation and inference.

Use Cases

Expert Systems and Diagnostic Reasoning

Prolog's original killer application was expert systems -- programs that encode domain expert knowledge as rules and use inference to provide diagnoses, recommendations, or explanations. Medical diagnosis systems (MYCIN-style), hardware fault analysis, and financial risk assessment all benefit from Prolog's natural ability to chain rules and explain its reasoning.

Natural Language Processing

Definite Clause Grammars (DCGs) in Prolog provide a notation for specifying context-free and mildly context-sensitive grammars. The grammar rules compile directly into Prolog clauses, and parsing becomes theorem proving. This approach powered many early NLP systems and remains relevant for domain-specific language parsing, command interpretation, and structured data extraction.

Formal Verification and Theorem Proving

Prolog's resolution-based inference is closely related to the proof search mechanisms in automated theorem provers. Systems like Isabelle, Coq, and Lean share theoretical foundations with Prolog, and many verification tools use Prolog or Prolog-derived engines internally for proof search and tactic execution.

Knowledge Graphs and Ontology Reasoning

Querying and reasoning over knowledge graphs maps directly to Prolog's fact-and-rule paradigm. OWL (Web Ontology Language) reasoners, RDF triple stores, and semantic web technologies draw heavily on logic programming foundations. The Prismatic Platform's knowledge representation layer uses patterns directly descended from Prolog's declarative knowledge base approach.

Security Policy Evaluation

Access control policies, firewall rules, and compliance requirements can be expressed as logical rules. Policy evaluation becomes proof search: "Can user X access resource Y?" translates to a Prolog query against the policy database. This approach provides formal guarantees about policy completeness and consistency that imperative implementations cannot easily achieve.

Logic programming and Prolog connect to numerous concepts within the Prismatic Platform ecosystem:

  • Inference -- the fundamental mechanism by which Prolog derives new knowledge from existing facts and rules, central to the platform's reasoning capabilities
  • Knowledge Representation -- Prolog's primary strength lies in representing structured knowledge as facts and rules, a pattern used throughout the platform's knowledge systems
  • Logical Reasoning -- Prolog embodies logical reasoning as computation, providing the theoretical foundation for the platform's epistemic validation
  • Logical Consistency -- Prolog programs must maintain logical consistency to produce sound results, directly paralleling the Trinity Gate's consistency requirements
  • Pattern Matching -- Prolog's unification generalizes Elixir's pattern matching, and both serve as primary mechanisms for data destructuring and control flow
  • Formal Verification -- Prolog's proof search mechanism is closely related to the automated reasoning used in formal verification systems including Lean4
  • Belief Graph -- the platform's belief tracking system uses graph structures and inference patterns inspired by Prolog's knowledge base architecture
  • Epistemic Reasoning -- Prolog provides a computational model for epistemic reasoning, enabling agents to reason about what is known, unknown, and derivable
  • Axiom Enforcement -- the NABLA framework's axiom system mirrors Prolog's fact assertion mechanism, where axioms serve as non-negotiable ground truths
  • Knowledge Graph -- modern knowledge graph systems descend from Prolog's fact-and-rule databases, using similar query and inference patterns

See Also

  • Elixir -- the platform's primary language, whose pattern matching and guard clauses echo Prolog's unification
  • BEAM VM -- the virtual machine underlying Elixir, which shares Prolog's origins in symbolic computation research
  • Property-Based Testing -- uses Prolog-inspired search over input spaces to find counterexamples
  • Trinity Gate -- the platform's three-layer verification system that applies logical consistency checking inspired by resolution-based reasoning

Built with precision. Powered by logic.

Prismatic Platform | Created by Tomas Korcak (korczis)