斯卡拉:溪流不是很懒惰?

时间:2012-11-16 04:50:47

标签: scala stream lazy-evaluation

我知道在Scala中流应该是懒惰的评估序列,但我认为我正遭受某种基本的误解,因为它们似乎比我预期的更加渴望。

在这个例子中:

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

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

3 个答案:

答案 0 :(得分:21)

Streams是懒惰的事实并没有改变方法论据被急切评估的事实。

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

虽然有几种方法可以让这个工作。由于您已将bad声明为lazy val,因此最简单的方法可能是使用延迟#:::流级联运算符来避免强制进行评估:

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

答案 1 :(得分:21)

好的,所以在评论其他答案后,我想我也可以将我的评论转化为正确的答案。

Streams确实是懒惰的,并且只会按需计算其元素(并且您可以使用#::按元素构造流元素,就像:: List一样。例如,以下内容不会抛出任何异常:

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

这是因为在应用#::时,会按名称传递尾部,以便不会急切地评估它,但仅在需要时(请参阅ConsWrapper.# ::const.apply和类{{1}在Cons中了解更多详情)。 另一方面,头部通过值传递,这意味着无论如何都会对其进行热切评估(如Senthil所述)。这意味着执行以下操作实际上会抛出ArithmeticException:

Stream.scala

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

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

答案 2 :(得分:4)

The Stream将对头部和头部进行评估。剩余的尾巴被懒惰地评估。在你的例子中,两个流只有头部和头部。因此给出了一个错误。