CI Docs License: MIT

New to Prolog or logic programming? This README assumes some familiarity with the vocabulary (facts, rules, unification, backtracking, cut...) since it's written for evaluating Aletheia as a dependency. guides/language/TUTORIAL.md explains every one of those terms from scratch, with plain-English walkthroughs and no assumed background at all — start there instead if any of the paragraph below is unfamiliar.

Aletheia is an embedded Prolog dialect for Elixir — Clojure is to Common Lisp as Aletheia is to Prolog. Classic Prolog surface syntax (:-, ,/;/->, =, double-quoted strings, DCG's -->, a runtime-mutable op/3 operator table — existing Prolog knowledge transfers directly, no bespoke syntax to learn) on top of the BEAM, via a reader built on Ichor's parsing substrate and Episteme, the standalone resolution engine and clause database (unification, SLD-resolution, backtracking, a genuine clause-scoped cut) this package is a syntax front-end for.

In plain English: this program stores four facts about who's a parent of whom, then a rulegrandparent(X, Z) holds whenever some Y exists with X a parent of Y and Y a parent of Z. Asking grandparent(tom, Who) doesn't specify Who at all; Aletheia backtracks through every value that makes the rule true and hands back all of them, not just the first.

{:ok, db} = Aletheia.consult_string("""
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).

grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
""")

Aletheia.query("grandparent(tom, Who)", db)
#=> {:ok, [%{"Who" => :ann}, %{"Who" => :pat}]}

See guides/language/TUTORIAL.md for a full walkthrough of the language, or guides/language/ALETHEIA_CHEATSHEET.md if you already know Prolog and just want the syntax at a glance. For embedding Aletheia into an Elixir application specifically, see guides/TUTORIAL.md instead.

Why embed a Prolog dialect in Elixir?

Some problems are naturally relational and searchy — "given these facts and rules, find every X that satisfies these constraints, backtracking through alternatives automatically" — a rules engine, an expert-system-style diagnosis/eligibility check, a permissions/access- control policy expressed as facts instead of nested conditionals, a small parser or configuration DSL (Aletheia ships real DCG support for exactly that), or a relational query over in-memory facts that would otherwise be a tangle of nested Enum.filter/Enum.flat_map calls hand-rolling what unification and backtracking already do for free. Prolog's own strength — a real, load-bearing distinction between facts (what's true), rules (what follows from what), and goals (what you're asking), searched exhaustively via backtracking rather than evaluated top-to-bottom like ordinary code — is exactly the shape those problems already have; forcing them into imperative control flow is usually the harder path, not the easier one.

Aletheia specifically (rather than reaching for Episteme directly) is for when you want that as real, classic Prolog syntax you can write, read, and consult from .alp source files — existing Prolog books/tutorials/ muscle memory transfer directly, and op/3 lets you extend the grammar itself at read time, the same way real Prolog does.

Components

   Aletheia source (.alp)
         │
         ▼
  Aletheia.Reader  ──── lib/aletheia/reader/grammar.aether (an Aether
         │               grammar), generated ahead of time via
         │               `mix ichor.gen` into grammar_generated.ex,
         │               with Ichor.Toolkit.Pratt wired in via @native
         │               for op/3-driven operator-precedence parsing
         ▼
   Episteme.Term IR  ──── Var/Compound structs + native lists/strings,
         │                implementing Ichor.Backtrack.Term
         ▼
  Episteme.Database  ──── clause storage, populated by consult
         │
         ▼
   Episteme.Engine  ──── SLD-resolution over Ichor.Backtrack.Tree/
         │                Bindings, plus Episteme's own cut-barrier
         │                mechanism and builtin predicate dispatch
         ▼
      solutions

Everything below Aletheia.Reader in that diagram — the term IR, clause database, resolution engine, cut, and builtin predicates — lives in Episteme, a separate package with no dependency on Aletheia's reader or concrete syntax at all. This package (Aletheia) is the syntax front-end: the reader plus a REPL.

  • Reader (Aletheia.Reader, Aletheia.Reader.Actions, Aletheia.Reader.Pratt) — parses source into clause forms ({:fact, _}/{:rule, _, _}/{:dcg, _, _}/{:directive, _}), one top-level clause at a time, threading the operator table op/3 directives mutate from each into the next. Aletheia.Reader.Pratt is the @native(...) callback doing the actual precedence-climbing; Aletheia.Reader.ControlSyntax bridges the reader's own ISO-punctuation term shapes (,/2, ;/2, =/2, ...) to the plain-English names Episteme.Engine dispatches on.
  • Term IR (Episteme.Term) — atoms/numbers/strings are plain Elixir atoms/integers-floats/binaries, lists are native Elixir lists (decomposing as ISO's own ./2 cons functor so unification can recurse into them), variables and compounds get dedicated structs.
  • Database (Episteme.Database) — clause storage, indexed by {name, arity}, pluggable backend (in-memory ETS by default, DETS for on-disk persistence).
  • Engine (Episteme.Engine) — the resolution engine: clause selection, subgoal sequencing, control constructs (and/or/ if_then/cut/not/call/N/once/ignore/phrase) over Ichor.Backtrack, plus a real clause-scoped cut (see the reference) and Episteme.Dcg (DCG rule/body translation).
  • Builtins (Episteme.Builtins.*) — arithmetic, exceptions, the full list-predicate family (including higher-order include/ exclude/foldl/maplist), aggregation (findall/bagof/ setof), the dynamic database (assert/retract/dynamic/ abolish/clause), the atom/number/string conversion family, and I/O (write/format/...).
  • REPL (Aletheia.Repl) — a consult + interactive query loop, genuinely lazy (pulls one solution at a time via query_lazy/2 + next_solution/2, never forcing more of a search than you actually ask to see via ;).

Aletheia depends on ichor_runtime directly (Ichor.Toolkit.Pratt, used by the reader itself), on ichor only at dev time (mix ichor.gen, ahead-of-time grammar codegen — see lib/aletheia/reader/grammar_generated.ex, never ships in a release build), and on episteme for everything past parsing (which in turn depends on ichor_runtime for Ichor.Backtrack/Ichor.Toolkit.TermWalk).

Installation

Not yet published to Hex. For now, add it as a path or git dependency alongside a checkout of this repository:

def deps do
  [
    {:aletheia, path: "../aletheia"}
  ]
end

Aletheia's own mix.exs depends on Episteme as a regular Hex dependency ({:episteme, "~> 0.2"}) — no sibling checkout needed.

Documentation

  • Generated API docs — module and function reference, rebuilt from main on every push.

  • guides/TUTORIAL.md — embedding Aletheia into an Elixir application, step by step: adding the dependency, consulting a program, querying it (eagerly, once, or lazily), errors at the host boundary, the REPL, and a worked example. Start here if you're new to Aletheia and already know Prolog.

  • guides/EXAMPLES.md — concrete, complete embedding examples (incremental consulting, multi-tenant database isolation, error handling, paginating a large search).

  • CASE_STUDY.md — a single larger, real-world worked example: a structured-log auditor combining a DCG grammar, the dynamic database, and grouped aggregation, written as one real .alp program, with the intent, solution, and result spelled out end to end.

  • guides/CHEATSHEET.md — quick reference for that same embedding API.

  • CHANGELOG.md — release history.

  • LICENSE — MIT.

  • Language tutorial and Language reference — everything about the Aletheia language itself (the Prolog dialect), independent of how it's embedded. Start here if you're new to Prolog.

  • Language examples and Language cheatsheet.

Development

mix deps.get
mix precommit

mix precommit runs the whole gate in one shot — format, compile --warnings-as-errors, credo --strict, sobelow --skip, test, dialyzer — the same checks CI runs, in fast-to-slow order. After editing lib/aletheia/reader/grammar.aether, regenerate the checked-in parser with MIX_ENV=dev mix gen.grammar before running the suite again.

See CHANGELOG.md for release history.

License

MIT — see LICENSE.