Scala - 为什么我应该用关键字 lazy 定义一个 Stream?

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

Streams 根据定义是惰性集合类型。但是当我查看使用Streams的示例时,似乎我在定义它们时仍然必须使用关键字lazy

示例:

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

为什么我需要这样做?当我执行以下命令时,我得到相同的结果:

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

如果没有看到您所指的具体示例,很难说太多,但使用 lazy 的原因之一是推迟对流头部的评估。例如,注意这之间的区别:

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

还有这个:

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

在第二种情况下,将立即打印消息,因为 #:: 评估流的头部。在任何情况下,您在定义流时都不会 "have to" 使用 lazy — 通常立即评估头部不是问题,或者实际上是您想要的行为。

没有Streams声明为lazy。由于几个原因,它是惯用的。

首先,Streams用于我们希望集合尽可能惰性的时候。使用 lazy 甚至可以延迟计算 Stream 的头部:

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  

这样也可以防止异常:

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

另一个问题是记忆。为了尽可能延迟对许多数据类型的执行,使用 def 代替 lazy val 是很自然的:

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

这里的问题是 Streams 很聪明地记住了他们的结果,但这需要我们有地方存储信息,而 val 允许我们这样做。所以如果我们这样做:

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.

所以本质上 lazy val 为我们提供了 def 的最终延迟执行,同时保留了 val 的记忆能力。您可以在 docs 中阅读有关 Streams 记忆的更多详细信息。