Gybelle
Gybelle

Reputation: 385

Scala - Why should I define a Stream with the keyword lazy?

Streams are a lazy collection type by definition. But when I look at examples that make use of Streams, it seems that I still have to use the keyword lazy when I define them.

Example:

lazy val myStream: Stream[Int] = 2 #:: Stream.empty

Why do I need to do this? When I execute the following, I get the same result:

val myStream: Stream[Int] = 2 #:: Stream.empty

Upvotes: 1

Views: 254

Answers (2)

Ben Reich
Ben Reich

Reputation: 16324

You don't have to declare Streams as lazy. It is idiomatic for several reasons.

First of all, Streams are used when we want a collection to be as lazy as possible. Using lazy allows for even the head of the Stream to be evaluated lazily:

def myExpensiveOperation = { println("computing..."); Thread.sleep(5000); 1 }

//Just declaring this stream variable causes the thread to sleep,  
//Even though we might not ever need to iterate through the stream
val stream = myExpensiveOperation #:: Stream.empty  

//Declaring the stream as lazy val solves this issue:
lazy val stream = myExpensiveOperation #:: Stream.empty  

This can also prevent exceptions:

val stream = (1/0) #:: Stream.empty //Throws exception
lazy val stream = (1/0) #:: Stream.empty //Safe

Another issue at play here is memoization. In order to defer execution as late as possible for many data types, it would be natural to use def instead of lazy val:

def streamDef = myExpensiveOperation #:: Stream.empty //No sleeping or printing!

The problem here is that Streams are smart about memoizing their results, but this requires us to have somewhere to store the information and a val allows us to do that. So if we do:

streamDef.toList //Sleep and print
streamDef.toList //Sleep and print again!

streamVal.toList //Sleep and print
streamVal.toList //No sleeping or printing!  The results have been memoized.

So essentially lazy val gives us the ultimate deferred execution of def while preserving the memoziation abilities of val. You can read more details about memoization of Streams in the docs.

Upvotes: 3

Travis Brown
Travis Brown

Reputation: 139028

It's hard to say much without seeing the specific examples you're referring to, but one reason to use lazy is to defer evaluation of the head of the stream. For example, note the differences between this:

lazy val myStream: Stream[Int] =
  { println("Evaluating head!"); 2} #:: Stream.empty

And this:

val myStream: Stream[Int] =
  { println("Evaluating head!"); 2} #:: Stream.empty

In the second case the message will be printed immediately, since #:: evaluates the head of the stream. In any case you don't "have to" use lazy when defining streams—often having the head evaluated immediately isn't a problem or is actually the behavior you want.

Upvotes: 3

Related Questions