How to use the Scala Stream class, a lazy version of List

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

See Also