ExDatalog.Rule (ExDatalog v0.5.0)

Copy Markdown View Source

A Datalog rule: head :- body [, constraints].

A rule consists of:

  • A head atom (ExDatalog.Atom.t()) — the relation being derived.
  • A body list of literals — each is one of:
    • {:positive, ExDatalog.Atom.t()} — a positive relational atom.
    • {:negative, ExDatalog.Atom.t()} — a negated relational atom.
  • A constraints list of ExDatalog.Constraint.t() — built-in predicates (comparisons and arithmetic) evaluated after joining body atoms.

Polarity is carried at the rule level, not in the atom itself. Constraints are kept separate from the body literal list for clarity and evaluation ordering.

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Y")])
iex> body = [{:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])}]
iex> Rule.new(head, body)
%ExDatalog.Rule{
  head: %ExDatalog.Atom{relation: "ancestor", terms: [{:var, "X"}, {:var, "Y"}]},
  body: [{:positive, %ExDatalog.Atom{relation: "parent", terms: [{:var, "X"}, {:var, "Y"}]}}],
  constraints: []
}

Summary

Functions

Returns the aggregate constraints in the rule.

Returns all body atoms (stripping polarity).

Returns the callback predicates in the rule body.

Returns true if the rule contains any aggregate constraint (count, sum, min, max).

Returns true if the rule contains any callback predicate in its body.

Returns true if the rule contains any negative body literals.

Returns all variable names that appear in the rule head.

Constructs a new rule with a head atom and body literals.

Returns the non-aggregate constraints in the rule.

Returns all variable names that appear in positive body atoms.

Returns all variable names that appear anywhere in the rule (head + body + constraints).

Types

literal()

@type literal() ::
  {:positive, ExDatalog.Atom.t()}
  | {:negative, ExDatalog.Atom.t()}
  | {:callback, ExDatalog.Callback.t()}

t()

@type t() :: %ExDatalog.Rule{
  body: [literal()],
  constraints: [ExDatalog.Constraint.t()],
  head: ExDatalog.Atom.t()
}

Functions

aggregate_constraints(rule)

@spec aggregate_constraints(t()) :: [ExDatalog.Constraint.t()]

Returns the aggregate constraints in the rule.

body_atoms(rule)

@spec body_atoms(t()) :: [ExDatalog.Atom.t()]

Returns all body atoms (stripping polarity).

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> a1 = Atom.new("parent", [Term.var("X"), Term.var("Y")])
iex> a2 = Atom.new("alive", [Term.var("X")])
iex> rule = Rule.new(Atom.new("ok", [Term.var("X")]), [{:positive, a1}, {:negative, a2}])
iex> Rule.body_atoms(rule)
[
  %ExDatalog.Atom{relation: "parent", terms: [{:var, "X"}, {:var, "Y"}]},
  %ExDatalog.Atom{relation: "alive", terms: [{:var, "X"}]}
]

callbacks(rule)

@spec callbacks(t()) :: [ExDatalog.Callback.t()]

Returns the callback predicates in the rule body.

has_aggregates?(rule)

@spec has_aggregates?(t()) :: boolean()

Returns true if the rule contains any aggregate constraint (count, sum, min, max).

Examples

iex> alias ExDatalog.{Rule, Atom, Term, Constraint}
iex> head = Atom.new("dept_count", [Term.var("D"), Term.var("N")])
iex> body = [{:positive, Atom.new("emp", [Term.var("E"), Term.var("D")])}]
iex> rule = Rule.new(head, body, [Constraint.count(Term.var("E"), Term.var("N"))])
iex> Rule.has_aggregates?(rule)
true

has_callbacks?(rule)

@spec has_callbacks?(t()) :: boolean()

Returns true if the rule contains any callback predicate in its body.

has_negation?(rule)

@spec has_negation?(t()) :: boolean()

Returns true if the rule contains any negative body literals.

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("bachelor", [Term.var("X")])
iex> body = [
...>   {:positive, Atom.new("male", [Term.var("X")])},
...>   {:negative, Atom.new("married", [Term.var("X"), :wildcard])}
...> ]
iex> Rule.has_negation?(Rule.new(head, body))
true

iex> alias ExDatalog.{Rule, Atom, Term}
iex> Rule.has_negation?(Rule.new(
...>   Atom.new("r", [Term.var("X")]),
...>   [{:positive, Atom.new("s", [Term.var("X")])}]
...> ))
false

head_variables(rule)

@spec head_variables(t()) :: [String.t()]

Returns all variable names that appear in the rule head.

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Z")])
iex> body = [{:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])}]
iex> Rule.head_variables(Rule.new(head, body))
["X", "Z"]

new(head, body, constraints \\ [])

@spec new(ExDatalog.Atom.t(), [literal()], [ExDatalog.Constraint.t()]) :: t()

Constructs a new rule with a head atom and body literals.

Body literals must be {:positive, atom} or {:negative, atom} tuples. Constraints default to an empty list.

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("reachable", [Term.var("X"), Term.var("Y")])
iex> body = [{:positive, Atom.new("edge", [Term.var("X"), Term.var("Y")])}]
iex> Rule.new(head, body)
%ExDatalog.Rule{
  head: %ExDatalog.Atom{relation: "reachable", terms: [{:var, "X"}, {:var, "Y"}]},
  body: [{:positive, %ExDatalog.Atom{relation: "edge", terms: [{:var, "X"}, {:var, "Y"}]}}],
  constraints: []
}

non_aggregate_constraints(rule)

@spec non_aggregate_constraints(t()) :: [ExDatalog.Constraint.t()]

Returns the non-aggregate constraints in the rule.

positive_body_variables(rule)

@spec positive_body_variables(t()) :: [String.t()]

Returns all variable names that appear in positive body atoms.

These are the "safe" bindings available for use in the head and constraints.

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("result", [Term.var("X")])
iex> body = [
...>   {:positive, Atom.new("a", [Term.var("X"), Term.var("Y")])},
...>   {:negative, Atom.new("b", [Term.var("Y")])}
...> ]
iex> Rule.positive_body_variables(Rule.new(head, body))
["X", "Y"]

variables(rule)

@spec variables(t()) :: [String.t()]

Returns all variable names that appear anywhere in the rule (head + body + constraints).

Examples

iex> alias ExDatalog.{Rule, Atom, Term}
iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Z")])
iex> body = [
...>   {:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])},
...>   {:positive, Atom.new("ancestor", [Term.var("Y"), Term.var("Z")])}
...> ]
iex> rule = Rule.new(head, body)
iex> Rule.variables(rule) |> Enum.sort()
["X", "Y", "Z"]