Collection aggregates: x[] and x[].f
An empty
[]after a collection reads an aggregate over its members:robots[]is how many there are,robots[].batteryis the sum of theirbattery. Put either in a formula and it stays live.
Kernel: this.me 4.2.1 (npm) ⓘ
Source: the parser AggregateOp / AggregateRef in path-expr.ts, the evaluator evaluateAggregate in aggregate.ts, change tracking aggregate-index.ts. TypeScript reference: Aggregates (TypeScript).
Each block below has a Run button. Run executes the blocks of this page in order on one new ME() from this.me 4.2.1, so later blocks build on earlier ones. The comment after each read is what 4.2.1 returned.
The two forms
| Form | Name | Value |
|---|---|---|
C[] |
count | how many members collection C has |
C[].f |
sum | the sum of field f over every member of C (f can be a path: C[].stats.hp) |
There are no other aggregates: no avg, min or max. An average is the sum divided by the count.
Read one directly
import { ME } from "this.me";
const me = new ME();
me.robots[1].battery(80); me.robots[1].ok(true);
me.robots[2].battery(50); me.robots[2].ok(true);
me.robots[3].battery(20); me.robots[3].ok(false);
me.robots[4].battery(90); me.robots[4].ok(true);
me("robots[]"); // 4
me("robots[].battery"); // 240
me("robots[].ok"); // 3
true counts as 1 and false as 0, so the sum of a boolean field is how many members have it set.
In a formula
me.fleet["="]("n", "robots[]");
me.fleet["="]("total", "robots[].battery");
me.fleet["="]("avg", "robots[].battery / robots[]");
me.fleet["="]("oks", "robots[].ok");
me("fleet.n"); // 4
me("fleet.total"); // 240
me("fleet.avg"); // 60
me("fleet.oks"); // 3
The collection path is written from the root, even when the formula is declared under fleet. See Limitations.
What explain() shows
An aggregate is one input of the formula, with kind: "aggregate" and an aggregate block:
me.explain("fleet.oks").derivation.inputs[0].aggregate;
me.explain("fleet.oks").meta.dependsOn; // ["robots[].ok"]
Full result of me.explain("fleet.oks") at this point (timestamp shortened):
{
"path": "fleet.oks",
"value": 3,
"expr": "robots[].ok",
"derivation": {
"expression": "robots[].ok",
"inputs": [
{
"label": "robots[].ok",
"path": "robots[].ok",
"kind": "aggregate",
"value": 3,
"origin": "public",
"masked": false,
"status": "resolved",
"aggregate": {
"collection": "robots",
"field": "ok",
"op": "sum",
"context": "public-view",
"coverage": "public-view",
"status": "resolved",
"members": 4,
"terms": 4,
"domain": "boolean"
}
}
]
},
"meta": {
"dependsOn": ["robots[].ok"],
"lastComputedAt": 1792117617602
}
}
aggregate field |
Meaning |
|---|---|
collection, field, op |
The reference as parsed: op is "count" or "sum"; field is null for a count. |
context, coverage |
"public-view": the aggregate reads what me.as(null) would see. |
status |
resolved, absent (empty collection), incomplete (a member is missing the field or it is not a number / boolean), deferred, cycle, unsupported, non-finite. |
members |
How many members the collection has. |
terms |
How many of them contributed a value. |
domain |
"boolean" when every term was a boolean, "number" otherwise, null with no terms. |
problems |
Present when not resolved: a count and a sample of the member paths at fault. |
It stays live
Writing one member recomputes the formulas that read the collection:
me.robots[3].ok(true);
me("fleet.oks"); // 4
me.explain("fleet.oks").meta.k; // 4
me.explain("fleet.oks").meta.recomputed; // ["fleet.n","fleet.total","fleet.avg","fleet.oks"]
me.explain("fleet.oks").meta.changed; // ["fleet.oks"]
me.explain("fleet.oks").meta.sourcePath; // "robots.3.ok"
Every aggregate on robots was recomputed (k is 4), and only fleet.oks changed. See Performance.
Booleans in arithmetic
An aggregate’s value is a number, so it composes with any operator:
me.fleet["="]("okPlusOne", "robots[].ok + 1");
me.fleet["="]("allOk", "robots[].ok == robots[]");
me("fleet.okPlusOne"); // 5
me("fleet.allOk"); // true
me.explain("fleet.oks").derivation.inputs[0].aggregate.domain; // "boolean"
me.explain("fleet.total").derivation.inputs[0].aggregate.domain; // "number"
Summing a derived field
A per-member formula declared with [i] produces a field on each member. The aggregate sums it:
me.fleet.threshold(60);
me.robots["[i]"]["="]("canTake", "battery > fleet.threshold && ok");
me.fleet["="]("available", "robots[].canTake");
me("fleet.available"); // 2
me.robots[2].battery(75);
me("fleet.available"); // 3
me.explain("fleet.available").meta.recomputed;
The last read returns ["robots.2.canTake","fleet.n","fleet.total","fleet.avg","fleet.oks","fleet.okPlusOne","fleet.allOk","fleet.available"]: the member’s own canTake, then every formula reading robots[].
A member without the field
A sum is all or nothing. A new member without ok makes robots[].ok undefined (never a partial sum), and explain names the member:
me.robots[5].battery(70);
me("fleet.n"); // 5
me("fleet.total"); // 335
me("fleet.avg"); // 67
me("fleet.oks"); // undefined
me.explain("fleet.oks").meta.unresolved; // {"reason":"incomplete","inputs":["robots[].ok"],"causes":[{"path":"robots[].ok","status":"incomplete"}]}
me.explain("fleet.oks").derivation.inputs[0].aggregate.problems; // {"count":1,"sample":[{"path":"robots.5.ok","kind":"missing"}]}
me.robots[5].ok(true);
me("fleet.oks"); // 5
Empty collections and unknown fields
me.fleet["="]("drones", "drones[]");
me.fleet["="]("nope", "robots[].nope");
me("fleet.drones"); // undefined
me.explain("fleet.drones").meta.unresolved; // {"reason":"missing-input","inputs":["drones[]"],"causes":[{"path":"drones[]","status":"absent"}]}
me("fleet.nope"); // undefined
An empty or missing collection is undefined, not 0.
Limitations
Paths are from the root. Inside fleet, squad[].hp does not read fleet.squad; write fleet.squad[].hp:
me.fleet.squad[1].hp(5);
me.fleet.squad[2].hp(7);
me.fleet["="]("hpRel", "squad[].hp");
me.fleet["="]("hpRoot", "fleet.squad[].hp");
me("fleet.hpRel"); // undefined
me("fleet.hpRoot"); // 12
Only []. Function calls, [*], ternaries, (bool) * 1, a selector before [] and a second [] all give undefined:
me.bad["="]("a", "sum(robots[].battery)");
me.bad["="]("b", "count(robots)");
me.bad["="]("c", "robots[*].battery");
me.bad["="]("d", "fleet.n > 2 ? 1 : 0");
me.bad["="]("e", "(robots[1].ok) * 1");
me.bad["="]("f", "robots[battery > 40][]");
me.bad["="]("g", "robots[].cells[]");
me("bad.a"); // undefined
me("bad.b"); // undefined
me("bad.c"); // undefined
me("bad.d"); // undefined
me("bad.e"); // undefined
me("bad.f"); // undefined
me("bad.g"); // undefined
me.explain("bad.f").meta.unresolved; // {"reason":"evaluation-failed","detail":"rejected-path-form"}
To count members that pass a test, declare the test per member with [i] and sum it (see Summing a derived field).
Other rules from the source:
- Aggregates read the public view: values under a secret scope are not counted, for every caller including the owner.
z.[].wandz[ ].ware rejected. A key literally named[]is written with a quoted selector:z["[]"].w.- A fixed index before the collection is fine:
robots[1].cells[].vsums the cells of robot 1. - The sum is exact: every term is added as an exact integer in units of 2^-1074 (BigInt) and the total is rounded once, ties to even (
exactSum).
Performance
A write under a collection recomputes every formula that holds an aggregate of that collection, and each recompute walks every member. The cost of one write grows with the number of members. Measured on Node 20 with this.me 4.2.1, 1000 members, one per-member [i] formula and one r[].hi aggregate:
const m = new ME();
for (let i = 1; i <= 1000; i++) m.r[i].b(50);
m.r["[i]"]["="]("hi", "b > 30");
m.f["="]("c", "r[].hi");
// 200 writes m.r[1 + k].b(k % 2 ? 10 : 60): 783.4 ms, 3.92 ms per write; m("f.c") → 900
Few aggregates on collections of hundreds are fine. For thousands of members written often, keep the aggregate on a smaller collection.
Tests
- tests/aggregate-path.test.ts: parsing, rejected forms, rendering (165 passed on 4.2.1)
- tests/aggregate-eval.test.ts: evaluation, exact sum, reactivity, explain (77 passed)
- tests/Security/root-secret-aggregate-paths.test.ts: secret scopes ×
[]paths (14 passed) - tests/aggregate/exact-sum-ref.mjs: the reference exact sum
Run them with npm run test:aggregates in Typescript/.
See also
.me Syntax · .me Operators · me.explain(path) · What is O(k)? · Warehouse Fleet (aggregates in a live demo)