Fluent Streams: A Library for Rich Iterables Manipulation
December 13, 2024

Fluent Streams: A Library for Rich Iterables Manipulation

artwork: https://code-art.pictures/

Imagine a library:

  • like in photo frameprovides many useful functions for iterative operations
  • Reads familiar, like a standard array iterative method
  • No attempt is made to replace JavaScript built-in functions, such as Array, Set,or iteration protocol
  • Like Java Streams, including Elective Clearly distinguish between “worthless” and “undefined as a value”
  • Compact size of bundle

I couldn’t find a library that could satisfy all these needs, so I created my small library – smooth smooth.


Talk is cheap. show code


The 3 most common words in the text

const words = ['Lorem', 'ipsum', /* ... */]
stream(words)
  .groupBy(word => word.toLowerCase())
  .map(([word, list]) => [word, list.length])
  .sortBy(([, length])  => -length)
  .take(3)
  .toArray()

// => ['ut', 3], ['in', 3], ['dolor', 2]
Enter full screen mode

Exit full screen mode


Relatively prime integers

// Endless stream of 2..999 integers
const randomInts = continually(() => 
  2 + Math.floor(Math.random() * 998)
)

randomInts
  .zip(randomInts)
  .filter(([a, b]) => gcd(a, b) === 1)
  .distinctBy(pair => stream(pair).sortBy(i => i).join())
  .take(10)

// => [804, 835], [589, 642], [96, 145], ...
Enter full screen mode

Exit full screen mode

  • Streams can be unlimited as shown, but you can limit them to the first n Items used take(n) method
  • Streams can be reused multiple times, even in parallel. This is because streams are stateless and only store references to inputs. State is only established when the stream produces an iterator.


Generate a deck of cards

const deck = streamOf('', '', '', '')
  .flatMap(suit =>
    streamOf<string | number>(
      'A',
      ...range(2, 11),
      'J',
      'Q',
      'K'
    ).map(rank => `${rank}${suit}`)
  )

// => 'A♠', '2♠', '3♠', ...
Enter full screen mode

Exit full screen mode


And play Texas holdem poker!

  const playersNum = 2
  const [flop, turn, river, ...hands] = deck
    .takeRandom(3 + 1 + 1 + playersNum * 2)
    .zipWithIndex()
    .splitWhen((_, [, j]) =>
      j === 3            // flop
      || j === 4         // turn
      || j >= 5          // river
         && j % 2 === 1  // ...players' hands
    )
    .map(chunk =>
      // Unzip index
      chunk.map(([card]) => card)
    )

// flop = ['3♦', '9♣', 'J♦']
// turn = ['4♣']
// river = ['7♦']
// hands = ['J♠', '4♥'], ['10♠', '8♥']
Enter full screen mode

Exit full screen mode

The first player flopped a pair of jacks, while the second player got a straight on the river. Who will win?


this must be cheap

All of the above can be achieved using only native data structures. However, code written using Fluent Streams reads better. While making code more readable is a perfectly valid goal, the cost of achieving it should be low in terms of cognitive load, package size, and performance.

Fluent Streams does just that! Here’s why:

  • No learning curve:The API feels familiar, similar to standard array methods. Adding is easy, removing is easy.
  • No need to reinvent: This library does not create new data structures or protocols – it builds on the already powerful capabilities of JavaScript.
  • Bundling has minimal impact: just Reduced to 8.5 kBit is very light. If your project already includes React and its accompanying libraries (which weigh hundreds of thousands of bytes), this addition will be almost unnoticeable.
  • Lazy processing: This library delays processing of items, which can reduce memory usage and improve the efficiency of long pipelines by avoiding unnecessary copies of intermediate data.


Things to note

Warehouse has been shipped untranslated to ES5. This decision was driven by a desire to keep the package size small, achieved by leveraging ES6+ features that enable iteration with very concise code – most notably dynamo. However, only Widely supported Language features are exploited.

If you are still compiling to ES5, you can use the library by compiling it yourself and adding a polyfill. Note, however, that this increases the size of the bundle and is not recommended. On the contrary, this could be a great opportunity relive Configure your build and take advantage of modern JavaScript capabilities.


Happy coding!

2024-12-13 05:59:45

Leave a Reply

Your email address will not be published. Required fields are marked *