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
kittens.compile.last // res0: Option[String] = Some(value = "Popcorn")
We can transform streams in similar ways to lists. For
instance, we can call
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
val manyKittens = kittens.repeat // manyKittens: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..)
"Popcorn" was the last element of the
stream. What is the last element of
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
val notSoManyKittens = manyKittens.take(10000000) // notSoManyKittens: Stream[[x >: Nothing <: Any] => Pure[x], String] = Stream(..) notSoManyKittens.compile.last // res2: Option[String] = Some(value = "Popcorn")
does finish after a cup of tea.