Aletheia

CIDocsLicense: 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 YandY 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.

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

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.