Reputation: 385
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
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
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