Aggregator: rewrite

Previously, we put both the logic of aggregation (e.g., to sum ints we
have a long accumulator and do a checked add with the new int and the
old sum) and the aggregation state (e.g., count/min/sum/..) in the same
object. This means that for every different key, we instantiate a new
Aggregator object with all of the relevant functions, etc.

Instead, change the interface for an aggregator to be not `add(value)`
meaning 'a new value to this aggregate', but rather `add(value, state)`
meaning 'use this aggregation logic to add a new value to an existing
state'.  Now we need only allocate a state object (akin to a C struct)
for each group. This should reduce memory consumption and leads to
simpler code -- approx 150 fewer lines.