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.
What fuses
Section titled “What fuses”| Operation | Fuseable | |
|---|---|---|
filter, map, flatMap | yes | inlined into the loop |
take, drop | yes | take triggers early exit |
reduce, find, every, some | yes | terminals |
sort, reverse, groupBy, uniq | no | need 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)Mechanics
Section titled “Mechanics”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.
See Benchmarks for how fusion compares to lodash, ramda, and native JS.