定义,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
在第二种情况下,消息将立即打印,因为#::
评估流的头部。在任何情况下,在定义流时,您都"不必"使用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
的记忆能力。 您可以在文档中阅读有关记忆Streams
的更多详细信息。