Modules
Top-level API for querying an already-built Episteme.Database:
query/2 solves a goal term against one, returning every solution's
variable bindings. No parser lives here -- a goal is always an
already-built term (Episteme.Term.new_var/1, %Episteme.Term.Compound{},
plain Elixir atoms/numbers/lists), and a database is built directly via
Episteme.Database.add_clause/2/add_fact/2, or via consult_forms/2
for a front-end that parses its own surface syntax into
{:fact, _}/{:rule, _, _} forms. Aletheia's reader is one such
front-end, built on top of this library; this library has no
dependency on it or on any particular concrete syntax.
Evaluates a Prolog arithmetic expression term to an Elixir number
under bindings -- is/2's own right-hand side, and both sides of
every arithmetic comparison (numeric_equal/2, </2, ...). Raises (via
Episteme.Builtins.Exceptions.prolog_throw/1) a standard type_error
for anything that isn't a number, a bound variable, or a recognized
evaluable functor.
Standard ISO error-term constructors (type_error/2, domain_error/2,
instantiation_error/1) plus the raw Elixir throw tag Episteme's own
throw/1/catch/3 (in Episteme.Engine, which needs to solve the
recovery goal) and every builtin that detects bad input raise through.
Minimal I/O: write/1 writeln/1 print/1 nl/0 -- canonical
functor(args) text via Episteme.Term.to_text/1 (no operator-aware
pretty-printing yet). Each succeeds exactly once, side effect aside.
The list predicate family -- length/2 append/3 member/2 reverse/2 nth0/3 nth1/3 last/2 -- over native Elixir lists (Episteme lists are
plain [h|t], not cons compounds). Hand-rolled-goal style,
generalized to real unification via Ichor.Backtrack.Bindings.
Clause storage: {name, arity} -> ordered list of {head, body}
clauses, body a single term (true for a fact, matching real
Prolog's own internal Head :- Body shape) -- looked up by
Episteme.Engine on every user-predicate call, and mutated in place by
Episteme.Engine's assert/asserta/assertz/retract/retractall
dispatch. Populated either directly (add_clause/2/add_fact/2,
building terms yourself with Episteme.Term) or via consult_forms/2,
for any front-end that parses its own surface syntax into
{:fact, _}/{:rule, _, _} forms (Aletheia's reader is one such
front-end, but this module has no dependency on it or any particular
concrete syntax).
The storage contract Episteme.Database delegates every clause-storage
operation to, so the database's actual persistence strategy is a
pluggable choice rather than baked into Episteme.Database itself.
Ships two implementations -- Episteme.Database.Backends.Ets (the
default: an in-memory ETS table, indexed by {name, arity}) and
Episteme.Database.Backends.Dets (the same shape, backed by a DETS
file on disk, for a database that should survive past the owning
process) -- and any third module implementing this behaviour works
too: pass it as Episteme.Database.new/1's :backend option.
An on-disk Episteme.Database.Backend, for a database that should
survive past the owning process (or the VM) -- same {name, arity}-keyed
:set shape as Episteme.Database.Backends.Ets, backed by :dets
instead. Requires a :file option (a binary or charlist path) on
Episteme.Database.new/1; :table names the DETS table itself
(defaults to a name derived from :file, so two databases opened
against different files don't collide).
The default Episteme.Database.Backend: an in-memory :ets :set
table keyed by {name, arity}, each row's value the predicate's own
clause list -- so clause order (what backtracking order depends on) is
exactly that Elixir list's order, never ETS's own unspecified
multi-row traversal order. :public access, so any process holding
the same Episteme.Database.t() value can read and mutate it, not
just the process that called init/1 -- there is no coordination
beyond what :ets itself serializes, so concurrent assert/retract
against the same predicate from multiple processes can race (a
read-modify-write on one row); concurrent access to different
predicates never conflicts.
SLD-resolution over Ichor.Backtrack.Tree + Ichor.Backtrack.Bindings
plus Episteme.Database -- clause selection is disjunction/2 folded
over a predicate's clauses in declared order, subgoal sequencing is
conjunction/2, both reused unmodified from Ichor. Control constructs
(and, or, if_then/if_then_else, cut, not/1, call/N,
once/1) live directly here rather than in a separate builtins
module: they turned out, once actually implemented, to be inseparable
from the cut-barrier machinery below, not independent predicates
dispatchable through a generic table. Every one of these is a plain
English name, not the traditional Prolog operator (,/;/->/!/
\+) it corresponds to -- Episteme has no reader of its own (see
Episteme's moduledoc), so there's no textual syntax for any of this
to look "conventional" against; a word you can actually remember beat
matching ISO Prolog's punctuation.
Episteme's term representation: atoms and numbers are plain Elixir
atoms/integers/floats, lists are native Elixir lists, variables and
compounds get dedicated structs. This module is the
Ichor.Backtrack.Term implementation passed explicitly to every
Ichor.Backtrack.Bindings call, plus the handful of whole-term
operations (deep resolve, rename-apart) built on
Ichor.Toolkit.TermWalk that unification itself doesn't need but the
engine does.
A compound term name(args...).
A logic variable: ref is its identity, name is display-only.