Why FArray
what FArray buys
The introduction led with fusion, FArray's headline trick, which has
its own section. This page is the plain library: no .fuse
anywhere, just eager FArray, and it already beats the field. Single ops tie a raw
array; chaining is where it pulls ahead, so that is what these benchmarks show, Int on the left and
String on the right.
The rivals throughout:
List— Scala's immutable linked list, notjava.util.ListVector— a persistent treeIArray— an immutable view of a raw arrayfs2.Chunk— the array-backed chunk from the fs2 streaming libraryzio.Chunk— the array-backed chunk from the ZIO streaming library
First the totals. Every size of every chained-operation benchmark class in the suite, eager FArray
only, no .fuse:
Three effects compound down a chain:
Boxing is paid per stage. A three-stage pipeline over boxed elements boxes at every crossing; chaining multiplies the tax rather than amortizing it. FArray's stages hand each other primitive arrays.
Structural stages cost nothing. A
take,droporreversein the middle of a chain is an O(1) lazy node for FArray and a full copy for every array-backed rival, a copy of data whose only purpose is to be consumed by the next stage.Big (calling) methods slow the rivals down. The array-backed collections run every lambda you pass to
map/filterthrough one shared, generic copy of that method. Pack several different lambdas into one method and the JIT can no longer keep that shared copy specialized, so it deoptimizes and slows down. FArray compiles each lambda into its own loop, so there is nothing shared to spoil. This is what turns a one-line benchmark tie into a loss in real code, detailed on the map page.
flatMap → filter → take
flatMap doubles the sequence, filter thins it, and take throws a chunk of it away. FArray's
take is an O(1) SliceNode, so it discards the tail for free — everyone else copies out what they
just finished building. With flat-leaf flatMap feeding an unboxed filter on top, eager FArray
runs this shape up to 22× past List and 6–10× past Vector and the Chunks, before a single
.fuse:
map → filter → fold
Transform, select, aggregate: the shape of half the collection code ever written. One boxed
crossing per stage for the field; int[] end to end for FArray:
take → drop → map → fold
Slice stages are FArray's freebie: a lazy window over the source, no copy. A map then transforms
through the window, and the fold reads straight out of the result: