Functions and reducers
Two kinds of function are callable without registering anything. A registered host function of the same name shadows either.
Aggregate reducers
You declare them under aggregates on the report or on a group.
| Reducer | Result | Empty |
|---|---|---|
sum:=expr |
total of expr |
0 |
count |
number of rows | 0 |
countDistinct:=expr |
distinct values of expr |
0 |
avg:=expr |
mean of expr |
null |
min:=expr |
smallest expr |
null |
max:=expr |
largest expr |
null |
first:=expr |
expr on the first row in scope |
null |
last:=expr |
expr on the last row in scope |
null |
count takes no expression. Every other reducer takes the form
name:=expression. Give avg, min, max, first and last a visible marker
where an empty set matters.
first and last are the only reducers that read the order of the rows they
fold, so what a scope gives them matters. A report folds after where, its
sort and its take. A group that declares a sort and no take folds its
partition before it orders it. Neither skips a null.
Run reducers
You declare them under run and read each one as run.<name> on a detail row.
They’re the eight above, plus one.
| Reducer | run.<name> on each row |
|---|---|
sum:=expr |
cumulative sum up to and including this row |
count |
1-based running count |
countDistinct:=expr |
distinct values seen so far |
avg:=expr |
cumulative mean so far |
min:=expr, max:=expr |
running extreme so far |
first:=expr |
expr of the first row in this scope |
last:=expr |
expr of this row — the identity, which is what @ already says |
prev:=expr |
expr of the previous row, null on the first |
Scope decides the reset. A run block on the report persists across the
document. A run block on a group starts again at each instance. Run names
can’t repeat across the report and nested groups, because every run value
shares one run object.
Scalar built-ins
One value in, one value out. These are function calls only, never a reducer
declaration, so "total": "round:[email protected]" is a definition error.
| Function | Result |
|---|---|
round(x, n?) |
x to n decimal places, halves away from zero |
floor(x, n?) |
x to n decimal places, towards negative infinity |
ceil(x, n?) |
x to n decimal places, towards positive infinity |
abs(x) |
the magnitude of x |
They round the decimal you wrote, not the double it’s stored as, so
round(2.675, 2) is 2.68.
n defaults to 0 and must be a number from 0 to 100. A fractional n
truncates to whole places. Any other n is an error naming the cell and the
call. A non-finite x passes through untouched rather than folding to
zero.
min and max are reducer names and aren’t scalar two-argument functions.
min(1, 2) is a render error saying so. trunc isn’t built in.
Inline reducers
A reducer also folds an array on the row itself.
{ "value": "{{ sum(@.lines, l => l.total) }}" }Registered functions
type FunctionRegistry = Record<string, (...args: any[]) => any>You pass the registry as the second argument to report or plan. Registered
functions count as trusted host configuration, unlike the definition that calls
them. A compiled report lists the ones it calls on report.functions, each as a
{ name, arity, doc? } signature in call-first-seen order.