fs2 is a library for functional streams. It gives us the
fs2.Stream
datatype and a huge set of operators for
working with it.
But what is a functional stream? You might have heard that it’s like a list, but infinite. Instead of describing one, it’s easier to show you:
val kittens = Stream("Mao", "Popcorn") // kittens: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..)
kittens
is a pure fs2 stream, but it’s a pretty
boring one. It looks very much like a list.
We need to run the stream to get a meaningful value. Suppose we’re
only interested in the last element of kittens
. We can run the
stream to get that element by calling the .compile.last
function.
kittens.compile.last // res0: Option[String] = Some(value = "Popcorn")
We can transform streams in similar ways to lists. For
instance, we can call take
and drop
on
them.
val specialCat = kittens.take(1) // specialCat: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..) specialCat.compile.last // res1: Option[String] = Some(value = "Mao")
"Mao"
is indeed a very special cat.
If you’re familiar with the Scala standard library, you’ll
know that we could have used List
to do everything we’ve
seen so far. But we can do many things with an
fs2.Stream
that we can’t do with a list. For instance,
fs2
has a repeat
operator:
val manyKittens = kittens.repeat // manyKittens: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..)
"Popcorn"
was the last element of the kittens
stream. What is the last element of manyKittens
?
manyKittens.compile.last
If you run this in your console, you’ll find that your program
will take quite some time. More than enough time to brew a cup of
tea, or go for a walk, or have a nap. You could leave it running for a week if you
liked, or a year if you cared to because it would never finish. The
manyKittens
stream is infinite.
You might think that fs2 has a different mode of operation when it
comes to infinity: maybe it switches the way it manipulates a finite
vs an infinite stream. But we can use
exactly the same operators on either sort of stream: take
is still available to us, as are
all the others.
We can use take
to construct an enormous finite
stream:
val notSoManyKittens = manyKittens.take(10000000) // notSoManyKittens: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..) notSoManyKittens.compile.last // res2: Option[String] = Some(value = "Popcorn")
Unlike manyKittens
, notSoManyKittens
does finish after a cup of tea.