Aggregates: count and exists in rule bodies
Area: Rules, aggregates & recursion Teaches: the aggregate forms
exists { B }andcount { B } cmp Ninside aderivebody — a subquery whose cardinality (or mere non-emptiness) drives the head. Aggregation is part of the rule language, not a separate report step. Prerequisites: concepts and<:specialization, andderiverule bodies (see first-class relations). Run:ox build examples/aggregate_count_v0 && ox derive examples/aggregate_count_v0 <predicate>
A data language that cannot count inside a rule pushes every cardinality question out to application code. Argon admits an aggregate atom as a subquery in the body: exists { B } is true when B has any solution, and count { B } yields the number of solutions so a comparison atom can filter on it.
What to read in company.ar
The corpus is three people, one of them a manager:
pub fact Person(alice);
pub fact Person(bob);
pub fact Person(carol);
pub fact Manager(alice);
exists { B } is a bare Boolean — the head fires iff the body has at least one solution:
pub derive any_person() :- exists { Person(p) };
pub derive any_manager() :- exists { Manager(p) };
count { B } cmp N compares the cardinality. The aggregate binds the count to a synthesized variable; the comparison atom is the filter:
pub derive at_least_three_persons() :- count { Person(p) } >= 3;
pub derive thin_management() :- count { Manager(p) } < 2;
pub derive huge_company() :- count { Person(p) } >= 100;
These heads are 0-ary: each is a propositional flag that either fires or does not.
Running it
ox derive <predicate> prints the derived tuples. A 0-ary head that fires returns exactly one tuple — the empty tuple (); one that does not fire returns none:
derive(any_person): 1 tuple(s) () — three Persons exist
derive(any_manager): 1 tuple(s) () — alice is a Manager
derive(at_least_three_persons): 1 tuple(s) () — count{Person} = 3, so >= 3 holds
derive(thin_management): 1 tuple(s) () — count{Manager} = 1, so < 2 holds
derive(huge_company): 0 tuple(s) — count{Person} = 3, so >= 100 fails
The decisive contrast is at_least_three_persons vs huge_company: the same count { Person(p) } subquery (which evaluates to 3) yields opposite verdicts because the threshold differs. The count is computed inside the rule, not handed to the rule.
Honest caveats (what runs today)
- v0 admits
count(and bareexists) with the comparison operators==,!=,<,<=,>,>=. The comprehension form (sum(expr for x in S where φ)), aggregators beyondcount/count_distinct, and nested aggregates are γ follow-ons of RFD 0011. - A 0-ary head models a proposition: one tuple means true, zero tuples means false. The single tuple is the empty tuple
(). - This package builds and runs under
ox; unlike its sibling aggregate_membership_join it is not yet pinned by a corpus test, so it documents the surface rather than a regression.