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/
abolish/dynamic 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, _, _}/{:dcg, _, _} forms (Aletheia's reader
is one such front-end, but this module has no dependency on it or
any particular concrete syntax). A {:dcg, head, body} form is a
DCG rule (Head --> Body) -- translated via Episteme.Dcg.translate_rule/2
into an ordinary clause before storage, same as assert/1
(Episteme.Engine) does for a -->/2-shaped clause term asserted
directly.
Storage itself is pluggable via Episteme.Database.Backend: new/1
defaults to Episteme.Database.Backends.Ets (in-memory, indexed by
{name, arity} -- O(1) lookup instead of a linear scan, and mutable
in place so assert/retract effects are visible to every holder of
the same t() value, including across separate Episteme.query/2
calls). Pass backend: Episteme.Database.Backends.Dets, file: path for
a database that persists to disk, or any other module implementing
Episteme.Database.Backend.
Because a t() now wraps a mutable resource (an ETS table, an open
DETS file, ...) rather than being a plain immutable value, call
close/1 when you're done with a database you don't want to leak --
an ETS-backed one is cleaned up automatically if its owning process
exits, but a DETS-backed one holds an open file handle until closed.
Summary
Functions
Completely removes {name, arity} -- unlike replace_clauses/4 with
[] (what retractall/1 builds on), defined?/3 becomes false
again afterward, so a later plain call raises existence_error
rather than just failing.
Like add_clause/2, but prepends -- the primitive asserta/1 builds on.
Releases whatever resources the backend holds (an ETS table, an open DETS file, ...).
Loads every {:fact, head}/{:rule, head, body}/{:dcg, head, body}
form into db -- the latter a DCG rule (Head --> Body), translated
via Episteme.Dcg.translate_rule/2 before storage. {:directive, _}
forms are ignored here -- a front-end reader's own directives (an
op/3 table mutation, say) are that front-end's concern, not the
database's.
Marks {name, arity} as defined without adding a clause -- what
dynamic/1 builds on. A later plain call to it fails (no matching
clauses) instead of raising existence_error, and assert/retract
can be used on it immediately. A no-op if it's already defined
(existing clauses are never touched).
Builds an empty database. opts[:backend] (default
Episteme.Database.Backends.Ets) picks the storage backend; any other
option is backend-specific and passed straight to its init/1 (e.g.
:file for Episteme.Database.Backends.Dets).
Replaces the entire clause list for {name, arity} -- the primitive Episteme.Engine's retract/1/retractall/1 build on.
Forces any buffered writes to durable storage. A no-op on the default in-memory backend.
Types
@type indicator() :: {atom(), non_neg_integer()}
@type t() :: %Episteme.Database{ backend: module(), state: Episteme.Database.Backend.state() }
Functions
@spec abolish(t(), atom(), non_neg_integer()) :: t()
Completely removes {name, arity} -- unlike replace_clauses/4 with
[] (what retractall/1 builds on), defined?/3 becomes false
again afterward, so a later plain call raises existence_error
rather than just failing.
Like add_clause/2, but prepends -- the primitive asserta/1 builds on.
@spec clauses_for(t(), atom(), non_neg_integer()) :: [clause()]
@spec close(t()) :: :ok
Releases whatever resources the backend holds (an ETS table, an open DETS file, ...).
Loads every {:fact, head}/{:rule, head, body}/{:dcg, head, body}
form into db -- the latter a DCG rule (Head --> Body), translated
via Episteme.Dcg.translate_rule/2 before storage. {:directive, _}
forms are ignored here -- a front-end reader's own directives (an
op/3 table mutation, say) are that front-end's concern, not the
database's.
@spec declare_dynamic(t(), atom(), non_neg_integer()) :: t()
Marks {name, arity} as defined without adding a clause -- what
dynamic/1 builds on. A later plain call to it fails (no matching
clauses) instead of raising existence_error, and assert/retract
can be used on it immediately. A no-op if it's already defined
(existing clauses are never touched).
@spec defined?(t(), atom(), non_neg_integer()) :: boolean()
Builds an empty database. opts[:backend] (default
Episteme.Database.Backends.Ets) picks the storage backend; any other
option is backend-specific and passed straight to its init/1 (e.g.
:file for Episteme.Database.Backends.Dets).
@spec replace_clauses(t(), atom(), non_neg_integer(), [clause()]) :: t()
Replaces the entire clause list for {name, arity} -- the primitive Episteme.Engine's retract/1/retractall/1 build on.
@spec sync(t()) :: :ok
Forces any buffered writes to durable storage. A no-op on the default in-memory backend.