A quick, no-prose lookup table for everything Episteme understands — both the plain Elixir functions you call directly, and the vocabulary of questions/statements ("goals," below) the engine can answer. If you're new to Episteme or to logic programming in general, read TUTORIAL.md first — it explains all of this from scratch, with plain-English walkthroughs; this page is for once you already know the concepts and just need to remember the exact name or argument order (for full detail and a worked example on any one row, see REFERENCE.md; for complete programs, see EXAMPLES.md). Every goal below is written foo(A, B) style for readability; build it as an Elixir value the same way TUTORIAL.md does — %Compound{name: :foo, args: [a, b]}. Unlike real Prolog, goal names here are plain English words (and, or, not, unify, ...), not ISO Prolog's punctuation operators (,, ;, \+, =, ...) — Episteme has no reader/text-syntax of its own for those operators to be conventional against, so a name you can actually remember won its case over matching ISO Prolog symbol-for-symbol.

Terms used on this page

Skip this if you've read TUTORIAL.md already — it's the same vocabulary, defined there at more length.

  • goal — a statement or question you ask the engine to prove, e.g. "is Tom Bob's parent?"
  • fact — a statement stored as unconditionally true.
  • rule (also called a clause) — a statement stored as true whenever some other goal holds; a fact is really just a rule whose condition is always true.
  • variable / unbound — a blank waiting to be filled in; "unbound" means the blank hasn't been filled in yet. "Binds" a variable means fills in that blank with a value.
  • unify / unification — the core matching operation: given two values (possibly containing blanks), figure out how to fill in blanks on either side to make them identical, or fail if there's no way to. Almost everything below is built out of this one operation.
  • backtracking — automatically undoing a choice and trying the next alternative when the current one doesn't lead anywhere; how the engine finds every answer, not just the first one it stumbles on.
  • cut-opaque — a boundary a cut (cut, "commit to this choice, stop backtracking past this point" — see TUTORIAL.md §6) cannot reach through; a cut used inside a cut-opaque goal only affects choices made inside that goal, never the caller's own.
  • functor — the name of a statement, together with how many fields it has (its arity) — parent/2 is shorthand for "a statement named parent with 2 fields." A predicate is the same idea: "the parent/2 predicate" means every fact and rule stored under that name and field count.
  • resolve — look up what a variable currently points to (recursing into every nested variable too, for "fully/deeply resolve"); a "resolved" term has had every one of its filled-in blanks replaced with the actual value.

Building terms (Elixir side)

Prolog shapeElixir
atom foo:foo
integer 4242
float 3.143.14
string "foo""foo" (a native Elixir binary -- a distinct type from atoms)
variable XTerm.new_var("X") (anonymous: Term.new_var(), name defaults to "_")
compound foo(A, B)%Compound{name: :foo, args: [a, b]}
list [1, 2, 3][1, 2, 3] (a native Elixir list)
list [H|T][h | t]
clause foo(X) :- bar(X){%Compound{name: :foo, args: [x]}, %Compound{name: :bar, args: [x]}} (a fact is {head, true})

Running a query

CallReturns
Episteme.query(goal, db){:ok, [solution, ...]} (every solution, eagerly) or {:error, term}
Episteme.query_once(goal, db){:ok, solution}, :none, or {:error, term}
Episteme.query_lazy(goal, db){stream, vars} — pass to next_solution/2 one at a time
Episteme.next_solution(stream, vars){:solution, solution, rest}, :none, or {:error, term}

A solution is %{"VarName" => resolved_term} for every named (non-"_") variable that appeared in the query.

Building a database

CallEffect
Database.new(opts \\ [])Empty database. opts[:backend] defaults to Backends.Ets; pass backend: Backends.Dets, file: path for on-disk storage.
Database.add_fact(db, head)Appends {head, true}.
Database.add_clause(db, {head, body})Appends a rule.
Database.add_clause_first(db, {head, body})Prepends (what asserta/1 builds on).
Database.consult_forms(db, forms)Bulk-loads {:fact, head} / {:rule, head, body} / {:directive, _} (ignored) forms.
Database.clauses_for(db, name, arity)[{head, body}, ...], in declared order.
Database.defined?(db, name, arity)Whether the predicate has ever been declared (even if currently empty).
Database.replace_clauses(db, name, arity, clauses)Overwrites the whole clause list — the primitive retract/retractall build on.
Database.sync(db)Flushes to durable storage now (a no-op on the default ETS backend).
Database.close(db)Releases the backend's resources (an open DETS file, an ETS table).

Control constructs

GoalMeaning
trueAlways succeeds, once.
fail / falseAlways fails.
cutCommits to the current clause and every choice made since entering it.
and(A, B)Conjunction.
or(A, B)Disjunction.
if_then_else(Cond, Then, Else)Commits to Cond's first solution; runs Then if it has one, Else otherwise.
if_then(Cond, Then)Like if_then_else/3 with no Else — fails outright if Cond fails.
not(G)Negation as failure: succeeds iff G has no solutions. Binds nothing.
once(G)Commits to G's first solution.
ignore(G)Like once/1, but never fails -- succeeds unchanged if G has no solution.
call(G, Extra...)Calls G with Extra args appended; cut-opaque.
findall(Template, G, List)Collects Template for every solution of G into List ([] if none). Cut-opaque, binds nothing else.
bagof(Template, G, List)Like findall/3, but fails (not List = []) on no solutions, and groups by G's free variables (not in Template, not Var^G-quantified), backtracking one group per binding.
setof(Template, G, List)Like bagof/3, but each group's List is sorted + deduped by standard order, and groups are ordered by witness.
forall(Cond, Action)Succeeds iff every solution of Cond has a solution of Action. Cut-opaque, binds nothing.

Matching and comparing values (unification)

GoalMeaning
unify(A, B)Unify.
not_unify(A, B)Succeeds iff A and B do not unify (binds nothing).
unify_with_occurs_check(A, B)Like unify/2, but fails instead of building a self-referential/infinite term.
equal(A, B)Structural equality on already-resolved terms (equal(1, 1.0) is false).
not_equal(A, B)Structural inequality.
order_less(A, B), order_greater(A, B)Standard order of terms: Var < Number < Atom < Compound (real Prolog @<, @>).
order_less_or_equal(A, B), order_greater_or_equal(A, B)Real Prolog @=<, @>=.
compare(Order, A, B)Unifies Order with <, =, or > per the standard order of terms.
copy_term(Term, Copy)Unifies Copy with Term, every still-unbound variable renamed apart (sharing preserved).

Type checks

GoalTrue for
var(X)An unbound variable.
nonvar(X)Anything else.
atom(X)A plain atom.
string(X)A real string -- never true for an atom with the same characters.
atomic(X)An atom, number, or string (not a variable, compound, or list).
number(X)An integer or float.
integer(X)An integer.
float(X)A float.
compound(X)A %Compound{} or a non-empty list.
callable(X)An atom, %Compound{}, or non-empty list.
is_list(X)A proper (nil-terminated) list.
ground(X)No unbound variable anywhere in X, at any depth.

Term construction and inspection

GoalMeaning
functor(Term, Name, Arity)Term bound: decomposes it. Term unbound: builds it from Name/Arity (Arity 0 unifies Term with Name; Arity > 0 needs an atom Name, builds fresh-variable args).
arg(N, Term, Arg)Arg is Term's Nth argument, 1-based. Out of range fails, not an error.
univ(Term, List)Real Prolog =... Term bound: List = [Name | Args] (or [Term] if atomic). Term unbound: builds Term from a fully-instantiated List.

Arithmetic

GoalMeaning
X is ExprEvaluates Expr, unifies X with the result.
numeric_equal(A, B)Numerically equal.
numeric_not_equal(A, B)Numerically unequal.
A < B, A > BNumeric ordering (kept as ordinary math symbols).
less_or_equal(A, B), greater_or_equal(A, B)Numeric ordering, inclusive.
between(Low, High, X)X bound: checks less_or_equal(Low, X) and less_or_equal(X, High). X unbound: enumerates every integer in [Low, High] on backtracking (no solutions if Low > High). Low/High are themselves arithmetic expressions.

Evaluable functors for is/2 and the comparisons above:

FunctorMeaning
+, -, *The usual, integer in, integer out unless a float is involved.
/Integer division if it divides evenly, else float.
//Integer floor division; domain_error(non_zero, 0) on divide by zero.
modFloored modulo (result has the divisor's sign); same zero-divisor error.
remTruncated remainder (result has the dividend's sign); same zero-divisor error.
**, ^Power. Integer base/non-negative integer exponent stays integer; anything else is a float.
abs(X)Absolute value.
sign(X)1, -1, or 0.
min(A, B), max(A, B)The usual.
sqrt(X)Always a float.
sin(X), cos(X), tan(X)Trig, radians.
exp(X)e^X.
log(X)Natural log (base e).
unary -X, +XNegation / identity.

Exceptions

GoalMeaning
throw(Term)Raises a Prolog exception carrying Term.
catch(Goal, Catcher, Recovery)Runs Goal; if it throws a ball that unifies with Catcher, runs Recovery with that unification in place.
type_error(Type, Culprit)Throws error(type_error(Type, Culprit), _).
domain_error(Domain, Culprit)Throws error(domain_error(Domain, Culprit), _).
instantiation_errorThrows error(instantiation_error, _).
existence_error(Type, Culprit)Throws error(existence_error(Type, Culprit), _).

Thrown automatically:

  • An unbound variable anywhere a goal or an arithmetic expression is evaluated → instantiation_error.
  • Calling a predicate with no clauses and no prior assert/retractall declaring iterror(existence_error(procedure, Name/Arity), _).
  • An unrecognized arithmetic functor, or a non-numeric operand → error(type_error(evaluable, _), _).

An uncaught throw surfaces from Episteme.query/2 as {:error, term} rather than escaping as a raw Elixir exception.

Dynamic database

GoalMeaning
assertz(Clause) / assert(Clause)Appends Clause (a bare term is a fact; Head :- Body a rule). Unbound variables in Clause become that stored clause's own private variables.
asserta(Clause)Same, but prepends.
retract(Clause)Removes the first stored clause whose head and body both unify with Clause; keeps the resulting bindings. Fails (no mutation) if nothing unifies. Deterministic on success — does not backtrack into further matches.
retractall(Head)Removes every clause whose head unifies with Head. Always succeeds, binds nothing, and — like real Prolog — leaves the predicate defined (with zero clauses) rather than undefined, so later calls fail rather than raising existence_error.
abolish(Name/Arity)Unlike retractall/1, undefines the predicate too -- a later call raises existence_error again.
dynamic(Name/Arity) (or dynamic([Ind, ...]))Declares defined with zero clauses, so a call fails instead of raising existence_error. No-op if it already has clauses.
clause(Head, Body)Enumerates every stored clause for Head's {name, arity} whose head unifies with Head, one per backtrack (Body = true for a fact). Head must be bound. Undefined predicate just fails.

Effects are visible immediately to every later call against the same Database.t(), including from separate Episteme.query/2 calls, and are not undone by backtracking.

Lists

GoalMeaning
length(List, N)Either direction: List bound gives N; N bound (with List unbound) builds a list of N fresh variables.
append(A, B, C)C = A ++ B. With A unbound and C bound, enumerates every split of C on backtracking.
member(X, List)Enumerates every element of List on backtracking.
reverse(A, B)Either direction.
nth0(Index, List, Elem)0-based. Index unbound enumerates every {index, element} pair.
nth1(Index, List, Elem)1-based, same modes as nth0/3.
last(List, Elem)Fails on [].
msort(List, Sorted)Sorts by standard order of terms, keeps duplicates.
sort(List, Sorted)Sorts and removes duplicates (compare/3 == =, not plain ==).
permutation(List, Perm)Enumerates every reordering, N! of them. List must be bound.
sum_list(List, Sum)Sum = 0 for [].
max_list(List, Max), min_list(List, Min)Fail on [].
list_to_set(List, Set)Dedups, keeps first-occurrence order (unsorted).

Higher-order list predicates

Goal is called via the same mechanism as call/N, once per element (or per row, across lists in lockstep), cut-opaque.

GoalMeaning
include(Goal, List, Kept)Elements where call(Goal, Elem) succeeds.
exclude(Goal, List, Kept)Elements where it doesn't.
foldl(Goal, List, V0, V) (also /5, /6 for 2–3 lists)Threads an accumulator: call(Goal, Elem, AccIn, AccOut) per element.
maplist(Goal, List1, ..., ListN)Succeeds iff call(Goal, E1_i, ..., EN_i) succeeds for every row. At least one list must be bound; others get built fresh.

Atom, number, and string conversions

atom_*/number_* work with atoms (and atom_chars/2 produces single-character atoms); string_*/split_string work with real strings instead (and string_chars/2 produces single-character strings) -- the two families are not interchangeable.

GoalMeaning
atom_codes(A, Codes), atom_chars(A, Chars)Atom <-> code list / single-char-atom list, either direction.
atom_length(Atomic, Len)Character count of any atomic term's text.
atom_concat(A1, A2, A3)A1+A2 bound: concatenates. A3 only: enumerates every split on backtracking.
sub_atom(Atom, Before, Len, After, Sub)Relates Atom to every substring; unbound args enumerate, bound ones narrow. Sub is always an atom.
char_code(Char, Code)Single-character atom <-> its code, either direction.
number_codes(N, Codes), number_chars(N, Chars)Number <-> code list / single-char-atom list, either direction; unparseable text raises type_error(number, _).
upcase_atom(A, U), downcase_atom(A, D)Case conversion.
atomic_list_concat(List, A)Concatenates a bound list of atomics (no splitting mode).
atomic_list_concat(List, Sep, A)List bound: joins with Sep. List unbound, A bound: splits A on Sep (deterministic, unlike atom_concat/3's enumeration).
atom_string(A, S), string_to_atom(S, A)Atom <-> string, either direction (same conversion, argument order swapped).
string_concat(S1, S2, S3)Same shape as atom_concat/3, but string results.
string_chars(S, Chars), string_codes(S, Codes)Same shape as the atom_* versions, but string/code results (string_chars/2's chars are strings, not atoms).
string_length(S, Len)Character count.
number_string(N, S)Like atom_string/2, for numbers.
split_string(S, SepChars, PadChars, SubStrings)Splits S on any char in SepChars ("" = don't split), then trims PadChars from each field's ends. Always strings.

I/O

GoalMeaning
write(Term)Prints canonical functor(args) text (via Term.to_text/1), no trailing newline.
writeln(Term)Same, with a trailing newline.
print(Term)Same as write/1.
nlPrints a newline.
format(Format, Args) (also /1, Args = [])Prints Format with each ~-directive consuming the next element of Args. A non-list Args is treated as [Args].

No operator-aware pretty-printing (e.g. 1+2 prints as +(1, 2)) — a front-end reader is where that would live.

format/2 directives: ~w/~p/~q (term text, all the same here), ~a (atom text), ~d (integer text), ~s (code-list/string text), ~i (skip an argument), ~n (newline), ~~ (literal ~). No column/radix directives (~t, ~|, ~Nr, numeric-prefixed ~Nd).

DCG (Definite Clause Grammars)

Head --> Body threads an incoming/outgoing difference-list pair (S0/S) through every nonterminal call. Episteme.Dcg does the translation; practical subset only -- terminals ([t1,...,tn]), {Goal} (embedded plain goal, no list threading), cut, and, or, if_then, if_then_else, not, a bare nonterminal (atom/compound), a variable body (phrase(Var, S0, S)). No call//N pushback.

Goal / formMeaning
phrase(Body, List)Translates Body, calls it with List as input and [] as output (must consume all of List). Cut-opaque.
phrase(Body, List, Rest)Same, but leaves whatever's unconsumed in Rest.
dcg_translate(Rule, Clause)Unifies Clause with the ordinary clause Rule (a Head --> Body term) translates to -- no storage.
{:dcg, head, body} (Database.consult_forms/2)Translates and stores, like {:rule, head, body} but for a DCG rule.
assert((Head --> Body))Same translation, applied to a -->/2-shaped clause term (same as :-/2 is already special-cased).

Episteme.Term (Elixir side)

FunctionMeaning
Term.new_var(name \\ "_")A fresh logic variable.
Term.resolve_deep(term, bindings)Fully resolves term and every value nested inside it.
Term.fresh_copy(term)Renames every variable in term apart, preserving sharing.
Term.structurally_equal?(a, b)Same shape/value on two already-resolved terms; an integer never equals a float.
Term.to_text(term)Canonical functor(args) text for an already-resolved term.