Out of our depth
fs2 gives us the tools to work with infinity: the
fs2.Stream datatype can describe an infinite list. But here’s the problem: infinity is impossible to fathom.
In functional programming, we’re
used to primitive data types like
String and algebraic data
Option. We can
even describe recursive data types, such as the Scala linked
List, but these
are still always finite: we can imagine how a list is laid out in
memory, regardless of how big it is.
What’s happening in memory in
Surely we don’t have ten million kittens in our computer at once? And
manyKittens? What’s stopping our JVM from
Thinking of streams as infinite lists doesn’t help us answer these questions. It doesn’t give us any insight into how streams work.
The challenge of composition
This leads to more problems. If we don’t know how an infinite stream works, we can’t get a sense of how to compose one.
If you’ve done enough functional programming, you might have tried
your hand at writing your own linked list datatype and your own
take function: you’d be able to describe how
take worked, and how it composed with other operators on
But we can’t easily describe how stream operators compose. What
exactly is happening when we
Try and figure out the difference between the following two streams:
Stream("Mao", "Popcorn").take(3).repeat Stream("Mao", "Popcorn").repeat.take(3)
There’s a big difference between them, but without an understanding of composition, we can’t explain why that is.
This gets even more confusing when we start working with side-effects. Here’s a sneak peak at some effectful code:
Stream("Mao", "Popcorn") .evalMap(eat) .evalMap(nap) .repeat .compile .last
nap are both suspended
side-effects, regardless of how they’re implemented, so the order in
which they happen is important. In what order do Mao and Popcorn eat
To answer that question, we need a better intuition of infinite streams.