Scala:流不懒惰?



我知道流应该是 Scala 中懒惰计算的序列,但我认为我遭受了某种根本性的误解,因为它们似乎比我预期的更渴望。

在此示例中:

val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial ++ bad) take 1)

我得到一个java.lang.ArithmeticException,这似乎是由零除法引起的。 我希望bad永远不会被评估,因为我只要求流中的一个元素。 怎么了?

好的,所以在评论了其他答案之后,我想我也可以把我的评论变成一个正确的答案。

流确实是懒惰的,并且只会按需计算它们的元素(您可以使用#::逐个元素构造流元素,就像::forList一样)。例如,以下内容不会引发任何异常:

(1/2) #:: (1/0) #:: Stream.empty

这是因为在应用#::时,尾巴是按名称传递的,这样就不会急切地评估它,而只是在需要时(有关更多详细信息,请参阅Stream.scala中的ConsWrapper.# ::const.apply和类Cons)。 另一方面,头部是按值传递的,这意味着无论如何,它总是会被热切地评估(如 Senthil 所述)。这意味着执行以下操作实际上会抛出 ArithmeticException:

(1/0) #:: Stream.empty

这是一个值得了解的关于溪流的陷阱。但是,这不是您面临的问题。

在您的情况下,算术异常甚至在实例化单个流之前发生。当在lazy val bad = Stream(1/0)中调用Stream.apply时,参数被急切地执行,因为它没有被声明为一个 by name 参数。Stream.apply实际上采用 vararg 参数,这些参数必须按值传递。 即使它是按名称传递的,ArithmeticException也会在不久后触发,因为如前所述,流的头部总是被早期评估。

流是懒惰的事实并没有改变方法参数被急切地计算的事实。

Stream(1/0)扩展到Stream.apply(1/0)。语言的语义要求在调用方法之前计算参数(因为Stream.apply方法不使用按名称调用的参数),因此它会尝试计算要作为参数传递给Stream.apply方法的1/0,这会导致 ArithmeticException。

不过,有几种方法可以让它工作。由于您已经将bad声明为lazy val,最简单的方法可能是使用同样懒惰的#:::流连接运算符来避免强制求值:

val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial #::: bad) take 1)
// => Stream(1, ?)

流将评估头部,剩余的尾部被懒惰地评估。在您的示例中,两个流都只有头部,因此给出了错误。

相关内容

  • 没有找到相关文章

最新更新