我知道流应该是 Scala 中的惰性评估序列,但我认为我正遭受某种基本误解,因为它们似乎比我预期的更渴望。
在这个例子中:
val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial ++ bad) take 1)
我得到一个java.lang.ArithmeticException
,这似乎是由零除法引起的。我希望这bad
永远不会得到评估,因为我只要求从流中获取一个元素。怎么了?
我知道流应该是 Scala 中的惰性评估序列,但我认为我正遭受某种基本误解,因为它们似乎比我预期的更渴望。
在这个例子中:
val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial ++ bad) take 1)
我得到一个java.lang.ArithmeticException
,这似乎是由零除法引起的。我希望这bad
永远不会得到评估,因为我只要求从流中获取一个元素。怎么了?
Streams 是惰性的这一事实并没有改变方法参数被热切评估的事实。
Stream(1/0)
扩展到Stream.apply(1/0)
. 该语言的语义要求在调用方法之前对参数进行评估(因为该Stream.apply
方法不使用按名称调用的参数),因此它会尝试评估1/0
以作为参数传递给Stream.apply
方法,这会导致您的 ArithmeticException .
不过,有几种方法可以让它工作。由于您已经声明bad
为 a lazy val
,最简单的方法可能是使用同样惰性的#:::
流连接运算符来避免强制评估:
val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial #::: bad) take 1)
// => Stream(1, ?)
好的,所以在评论其他答案之后,我想我也可以将我的评论变成一个正确的答案。
流确实是惰性的,并且只会按需计算它们的元素(您可以使用它#::
来逐个元素地构造流,就像::
for一样List
)。例如,以下不会引发任何异常:
(1/2) #:: (1/0) #:: Stream.empty
这是因为在应用时#::
,尾部是按名称传递的,因此不会急切地评估它,而是仅在需要时才进行评估(有关更多详细信息,请参见ConsWrapper.# ::
和const.apply
class Cons
in Stream.scala
)。另一方面,head 是按值传递的,这意味着它总是会被急切地评估,无论如何(正如 Senthil 所提到的)。这意味着执行以下操作实际上会引发 ArithmeticException:
(1/0) #:: Stream.empty
这是一个值得了解的关于流的问题。但是,这不是您面临的问题。
在您的情况下,算术异常甚至在实例化单个 Stream 之前发生。当调用Stream.apply
in 时lazy val bad = Stream(1/0)
,该参数被急切地执行,因为它没有被声明为按名称参数。Stream.apply
实际上需要一个可变参数,并且这些参数必须按值传递。即使它是按名称传递的,ArithmeticException
也会在不久之后触发,因为如前所述,Stream 的头部总是被提前评估。
Stream 将评估头部,而剩余的尾部则懒惰地评估。在您的示例中,两个流都只有头部,因此出现错误。