LazyList: A Lazy Version of List

In this lesson, you will be introduced to LazyLists; the lazy version of Lists.

Introduction

A LazyList is an immutable sequence type collection very similar to Lists. What makes a LazyList lazy is that it only computes required elements, allowing it to be infinitely long. Other than this difference, LazyLists have the same performance characteristics as Lists, hence, to avoid repetition, we won’t discuss them here.

Creating a LazyList

Similar to the :: method used for creating Lists, LazyLists are created with the #:: method. The first element is the head while all other elements are collectively known as the tail. We use LazyList.empty to end a LazyList, which is equivalent to the Nil used for ending a List.

Press + to interact
val myFirstLazyList = 1.5 #:: 2.5 #:: 3.5 #:: LazyList.empty
myFirstLazyList.foreach(println)

Lazy Computation

A LazyList’s lazy aspect might not have been visible in the example above because we are asking the compiler to print every element of the LazyList; hence, every element needs to be computed. But what would happen if we tell the compiler to print the complete LazyList, instead of printing each element?

To get a better understanding of lazy computation, let’s first print a whole List and see how a nonlazy approach would look.

Press + to interact
val wholeList = "orange"::"banana"::"apple"::"grape"::Nil
print(wholeList)

The output shows a List of four elements. Now let’s print a LazyList with the exact same elements.

Press + to interact
val wholeLazyList = "orange" #:: "banana" #:: "apple" #:: "grape" #:: LazyList.empty
print(wholeLazyList)

The output shows a LazyList, but in place of the elements, there is a <not computed> statement. This is because the elements have not yet been computed.

But when would a LazyList actually come in handy? Imagine you want to create a list of elements from 1 to 100,000,000. Let’s see what would happen if you use a simple List.

Press + to interact
val list = List.from(1 to 100000000)

When you run the code snippet above, it will display Execution Timed Out! This is because when a List is declared, it computes every element in that List regardless of if we require them or not.

Now, let’s look at the LazyList implementation.

Press + to interact
val lazyList = LazyList.from(1 to 100000000)

Wow! It barely took any time for the above code snippet to execute. This is the power of LazyLists.

Streams

LazyList was introduced in the latest version of Scala, i.e., 2.13.0. In older versions, instead of LazyLists, Streams are used. They are identical to LazyLists in almost every way. The only difference is that while LazyLists are completely lazy, Streams compute the head element even when not required.


With LazyLists, we end our discussion on collections. Let’s wrap up this chapter with a quiz to test what you have learned so far.