This is an excerpt from the 1st edition of the Scala Cookbook (partially modified for the internet). This is Recipe 11.6, “How to Use the Scala Stream Class, a Lazy Version of a List”
Problem
You want to use a collection that works like a List
but invokes its transformer methods (map
, filter
, etc.) lazily.
Solution
A Scala Stream is like a List
, except that its elements are computed lazily, in a manner similar to how a view creates a lazy version of a collection. Because Stream
elements are computed lazily, a Stream
can be long ... infinitely long. Like a view, only the elements that are accessed are computed. Other than this behavior, a Stream
behaves similar to a List
.
UPDATE: The Scala LazyList class is replacing the older
Stream
class that’s shown in this article. That being said, most of the methods shown here (if not all of them) still have the same names.
Just like a List
can be constructed with ::
, a Stream
can be constructed with the #::
method, using Stream.empty
at the end of the expression instead of Nil
:
scala> val stream = 1 #:: 2 #:: 3 #:: Stream.empty stream: scala.collection.immutable.Stream[Int] = Stream(1, ?)
The REPL output shows that the stream begins with the number 1
but uses a ?
to denote the end of the stream. This is because the end of the stream hasn’t been evaluated yet. For example, given a Stream
:
scala> val stream = (1 to 100_000_000).toStream stream: scala.collection.immutable.Stream[Int] = Stream(1, ?)
you can attempt to access the head and tail of the stream. The head is returned immediately:
scala> stream.head res0: Int = 1
but the tail isn’t evaluated yet:
scala> stream.tail res1: scala.collection.immutable.Stream[Int] = Stream(2, ?)
The ?
symbol is the way a lazy collection shows that the end of the collection hasn’t been evaluated yet.
As discussed in Recipe 10.24, Creating a Lazy View on a Collection, transformer methods are computed lazily, so when transformers are called, you see the familiar ?
character that indicates the end of the stream hasn’t been evaluated yet:
scala> stream.take(3) res0: scala.collection.immutable.Stream[Int] = Stream(1, ?) scala> stream.filter(_ < 200) res1: scala.collection.immutable.Stream[Int] = Stream(1, ?) scala> stream.filter(_ > 200) res2: scala.collection.immutable.Stream[Int] = Stream(201, ?) scala> stream.map { _ * 2 } res3: scala.collection.immutable.Stream[Int] = Stream(2, ?)
However, be careful with methods that aren’t transformers. Calls to the following strict methods are evaluated immediately and can easily cause java.lang.OutOfMemoryError errors:
stream.max
stream.size
stream.sum
Transformer methods are collection methods that convert a given input collection to a new output collection, based on an algorithm you provide to transform the data. This includes methods like map
, filter
, and reverse
. When using these methods, you’re transforming the input collection to a new output collection. Methods like max
, size
, and sum
don’t fit that definition, so they attempt to operate on the Stream
, and if the Stream
requires more memory than you can allocate, you’ll get the java.lang.OutOfMemoryError.
As a point of comparison, if I had attempted to use a List
in these examples, I would have encountered a java.lang.OutOfMemory error as soon as I attempted to create the List
:
val list = (1 to 100_000_000).toStream
Using a Stream
gives you a chance to specify a huge list, and begin working with its elements:
stream(0) // returns 1 stream(1) // returns 2 // ... stream(10) // returns 11
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |