Scala - 为什么要用关键字 lazy 定义一个 Stream


根据

定义,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的更多详细信息。

相关内容

  • 没有找到相关文章

最新更新