DEV Community

Cover image for Fluent Streams: A Library for Rich Iterables Manipulation
Aleksei Berezkin
Aleksei Berezkin

Posted on • Edited on

Fluent Streams: A Library for Rich Iterables Manipulation

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

Imagine a library that:

  • Like Ramda, provides many useful functions for iterables manipulation
  • Reads familiar, like standard Array iterative methods
  • Doesn't try to replace JavaScript built-ins like Array, Set, or the iteration protocol
  • Like Java Streams, includes Optional to clearly distinguish between “no value” and “undefined as a value”
  • Is compact in terms of bundle size

I couldn't find one that fits all these needs, so I created my small library — fluent-streams.

Talk is Cheap. Show Me the Code

Top 3 Words in a Text

const words = ['Lorem', 'ipsum', /* ... */]

stream(words)
  .groupBy(word => word.toLowerCase())
  .map(([w, {length}]) => [w, length])
  .sortBy(([, length])  => -length)
  .take(3)
  .toArray()

// => ['ut', 3], ['in', 3], ['dolor', 2]
Enter fullscreen mode Exit fullscreen mode

Coprime 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 fullscreen mode Exit fullscreen mode
  • Streams can be endless, as shown, but you can limit them to the first n items using the take(n) method
  • A stream can be reused multiple times, even in parallel. This is because streams are stateless and only store a reference to the input. The state is created only when the stream spawns 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 fullscreen mode Exit fullscreen mode

And Play Hold'em Poker!

const playersNum = 2

const [flop, turn, river, ...hands] = deck
  .takeRandom(3 + 1 + 1 + playersNum * 2)
  .splitWhen((_l, _r, i) =>
    i === 2             // flop
    || i === 3          // turn
    || i >= 4           // river
      && i % 2 === 0    // ...players' hands
  )

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

First player has a pair of Jacks on the flop, while the second player gets a straight, but only on the river. Who will win?

This Must Be Cheap

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

And that’s exactly the case with Fluent Streams! Here’s why:

  • No learning curve: The API feels familiar, resembling standard Array methods. It’s easy to add and just as easy to remove.
  • No reinvention: The library doesn’t create new data structures or protocols — it builds on JavaScript’s already robust features.
  • Minimal bundle impact: At just near 9 kB minified, it’s lightweight. If your project already includes React and its satellite libraries, which weigh hundreds of kilobytes, this addition is hardly noticeable.
  • Lazy processing: The library processes items lazily, which can reduce memory usage and improve efficiency in long pipelines by avoiding unnecessary intermediate data copying.

Caveats

The library is shipped untranspiled to ES5. This decision is driven by the desire to maintain a small bundle size, which is achieved by leveraging ES6+ features that enable iteration with very concise code — most notably generators. However, only widely supported language features are utilized.

If you are still transpiling to ES5, you can use the library by transpiling it yourself and adding polyfills. Be aware, though, that this will increase the bundle size, so it’s not recommended. Instead, this might be the perfect occasion to revisit your build configuration and embrace modern JavaScript features.

Have Fun Coding!

Top comments (2)

Collapse
 
buckelieg profile image
Anatoly

Check this out

Collapse
 
alekseiberezkin profile image
Aleksei Berezkin

Great lib. Its successor, Sequency, was one of my inspirations