The optimizer
the optimizer
Fusing stages into one loop removes the plumbing tax. This page is about the optimizer, the part that removes work you wrote but never needed. Its organizing idea: a product (a tuple, a case class (Scala's record type), later a JSON record) is treated as a set of independent columns, and each column is computed at its first read or not at all. Everything below falls out of that idea; every emitted block is a golden test.
Dead-column elimination
A tuple or case class is a set of independent columns. Here map builds three, then the pipeline
reads only two of them. The optimizer never builds the tuple: column 2 (x * 13) is read by nobody,
so it's dead and absent from the loop; column 0 (x % 3) is needed by the filter, so it's
computed eagerly. A View (Scala's runtime-lazy collection wrapper, its analogue of a
java.util.stream pipeline) allocates the 3-tuple (plus three boxed Ints) for every element, and
throws most of it away.
you write
the macro emits
Compute-for-survivors: the sink
expensive(x) lands inside the if. Column 1 is needed only by the final map,
which sits downstream of the filter, so it's computed only for elements that survive. An expensive
column behind a selective filter runs a fraction of the time, an optimization the lazy-collection
model can't express, because it has no view of which columns a later stage will read.
you write
the macro emits
Common-subexpression elimination
x * x is written twice but computed once: bound to one val and reused across both tuple
components. Decomposition, dead-column elimination, the sink, and CSE all fall out of a single idea:
model a product as independent lazy columns, and bind each at its first use.
you write
the macro emits
…and the decomposition reaches the fold's lambda
The same column analysis reaches into a foldLeft/reduce lambda (foldLeft is a seeded reduce:
it starts from an initial value and folds left to right, so the result type can differ from the
element type). Here the fold reads only
s.score, so the optimizer never builds the Stat: its other fields (and the object itself) are
dead and absent, and the loop is just acc = acc + (x * 100). Without this, the fold would rebuild
the entire product per element just to read one field.
you write
the macro emits
Emitted blocks are the verbatim post-typer expansion (FuseDebug.show), regenerated to current
codegen and checked into tests/snapshots/; they are shown, not executed.