Ask three libraries for the sum of the same float array and you can get three answers — not wrong ones, just different fold orders rounding differently. Mica refuses the roulette: every reduction walks one documented tree, and that tree is part of the language’s contract.

The example is examples/Reductions. Build and run it:

make -C examples/Reductions run

The tree

A span is cut into blocks of 128 consecutive elements (ReductionBlock, an exported constant); a block folds left to right; block results combine pairwise, the split handing the left side half the blocks rounded down. Two consequences fall out by construction:

  • serial = parallel, bit for bit — carriers compute whole subtrees at fixed boundaries, and the join replays the same combines;
  • a float sum’s rounding error grows with the logarithm of the length, where a plain left fold grows it linearly.

numpy and Julia both ship pairwise summation; Mica’s difference is that the tree is documented and promised, not an implementation detail free to drift.

One verb family, every one-dimensional shape

Sum(series)                  { a fixed array }
Sum(ramp)                    { a Vector }
Sum(grades)                  { a dynamic array }
Sum(Span(series, 0, 4))      { any sub-range, through composition }
Sum(Span(m, 0, 5))           { a Matrix: its elements as one row-major span }

The verbs take the language’s own borrowed view, so anything that admits into a span reduces — and the Span composition you write is literally the recursion spine the tree itself runs on. A matrix has no second verb family: its elements admit as one contiguous span, numpy’s semantics on Mica’s admission machinery.

The comparison verbs ride the same tree

Minimum, Maximum, ArgMin, ArgMax carry (value, index) pairs up the same tree, and ties answer the first winning index — the only order-free deterministic answer:

marks[1] := 9;  marks[3] := 9;
at := ArgMax(marks) on fail use -1;      { 1 — the first 9 wins }

The empty view splits by the error charter

The sum of nothing is the additive zero, because mathematics says so. A mean, an extreme, or a winning index over nothing does not exist — so those verbs carry fails MathError and the call site answers the absence:

WriteLn("  Sum(empty) = %lf", Sum(empty));       { 0.000000 }
total := Mean(empty) on fail use -1.0;           { the absence, answered }

A fixed array or a tensor is never empty — a dimension counts at least one element — so the empty case arrives exactly where it can: dynamic data and narrowed windows.

What this does not do

  • No axis parameter. A runtime axis cannot select a compile-time shape. Per-axis reductions are spelled as views — Sum(RowView(address m, r)) — which is Eigen’s design, one verb over one span.
  • No integral Mean. Mean demands a fractional element (5210 at the call otherwise); an integral mean spells its conversion, keeping float32 fidelity intact.
  • No tolerance anywhere. The tree is exact IEEE at every node; comparing against an expectation uses the same arithmetic the runtime uses.

Try it

Replace Sum(series) with a hand-written left fold and compare: the two answers differ in the last bits, and the tree’s is the one the parallel run reproduces. The multicore proof is the harness’s ReductionMulticore case — four tasks computing the tree’s own quarters, joined bit-exactly at every carrier count.

Next

The verbs read views; the slicing page is where views and copies get their names.