Aggregate constraint evaluation: count, sum, min, max.
Aggregates are not evaluated per binding. Unlike comparison or arithmetic
constraints, an aggregate must see the full set of surviving bindings for a
rule, group them, and reduce each group to a single value. That grouping is
performed by ExDatalog.Engine.Evaluator via group_and_reduce/5; the
evaluate/3 callback exists only to satisfy the ExDatalog.Constraint
behaviour and raises if ever invoked through the per-binding pipeline.
Aggregates are integer-only. sum, min, and max require integer inputs,
guarded at runtime: a non-integer input raises ArgumentError from the
reducer (build-time type enforcement is future work). count returns the
group size regardless of input type. min/max return the smallest/largest
input value in the group.
Summary
Functions
Not supported per-binding. Aggregates are evaluated by the engine's group-and-reduce path. Always raises.
Groups bindings by group_vars and reduces each group's input_var values
with the aggregate op, binding the reduced value to result_var.
Functions
Not supported per-binding. Aggregates are evaluated by the engine's group-and-reduce path. Always raises.
Groups bindings by group_vars and reduces each group's input_var values
with the aggregate op, binding the reduced value to result_var.
Returns one extended binding per non-empty group. Empty groups never occur in
practice: a group exists only because at least one binding produced its key.
The min/max reducers still carry a defensive empty-list clause that raises
a clear error should that invariant ever be violated.
Examples
iex> bindings = [%{"D" => :eng, "E" => :a}, %{"D" => :eng, "E" => :b}, %{"D" => :ops, "E" => :c}]
iex> ExDatalog.Constraints.Aggregate.group_and_reduce(bindings, ["D"], :count, "E", "N")
...> |> Enum.map(fn b -> {b["D"], b["N"]} end)
...> |> Enum.sort()
[{:eng, 2}, {:ops, 1}]