Skip to content

Fusion

When you chain array operations in pipe, it fuses them into a single loop.

Without fusion:

data [100K] → filter → [50K] → map → [50K] → take(10) → [10]

Three passes. Two throwaway arrays.

With fusion:

data [100K] → fused(filter + map + take) → [10]

One pass. take(10) breaks the loop after 10 results.

OperationFuseable
filter, map, flatMapyesinlined into the loop
take, dropyestake triggers early exit
reduce, find, every, someyesterminals
sort, reverse, groupBy, uniqnoneed the full array

Operations that need the whole array break the chain. Everything before them gets fused and runs first, then the non-fuseable op runs on that result:

pipe(
data,
A.filter(x => x > 3), // ─┐ segment 1
A.map(x => x * 2), // ─┘ materializes here
A.sortBy((a, b) => a - b), // runs on the materialized array
A.take(3), // ── segment 2
)

Every function created by dual() carries metadata: _op, _fn, _args. pipe reads these tags and compiles consecutive fuseable ops into a single for loop with inlined predicates. take(n) emits a HALT to break out early.

Your own lambdas pass through unchanged. If nothing is fuseable, pipe just applies functions one after another like you’d expect.