Whether you run them inside an aggregation is up to you. These predicates are intended to alter the solution sequence generated by a non-deterministic predicate. This allows you to specify new non-deterministic predicates as simple logical conjunctions and disjunctions of already defined primitives, even if intermediate steps are normally required to (notably) improve efficiency. These predicates also support SWISH, which represents solutions in a table, much like SQL clients do.
For example, suppose we have a/1 and b/1 and want to get the solutions for a(X),b(X). Now suppose a/1 is cheap but produces a lot of duplicate answers and b/1 is expensive. Using this library we simply write distinct(a(X)), b(X)
and we are done. Without you get
findall(X,a(X),Xs), sort(Xs,Us), member(X,Us), b(X).
(or setof, but that may have its own problems). Besides being more compact, distinct also produces answers immediately instead of enumerating all answers for a/1. In addition, there is reduced/1 which doesn’t guarantee full uniqueness but runs in limited memory.
These operators combine nicely and allow for example processing the best 10 a(X) using b/1 in a nice concise and (IMO) readable syntax:
limit(10, order_by([desc(X)],a(X))),
b(X).
The group_by/4 is pretty handy if you wants existential qualification for all variables except a few, notably if variables may be hidden in the arguments you pass to bagof/3. Library yall provides a good alternative, I think as bagof(X, {V1,V2,...}>>Goal, Xs)
. This predicate was most of all added to complete the common vocabulary to manage rows in database answers.