Scala:流不懒惰?

Posted

技术标签:

【中文标题】Scala:流不懒惰?【英文标题】:Scala: Streams not acting lazy? 【发布时间】:2012-11-04 19:52:49 【问题描述】:

我知道在 Scala 中流应该是惰性求值的序列,但我认为我遇到了某种根本性的误解,因为它们似乎比我预期的更热切。

在这个例子中:

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

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

【问题讨论】:

【参考方案1】:

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

流确实是惰性的,只会按需计算其元素(您可以使用#:: 逐个元素构造流,就像:: 用于List)。例如,以下不会抛出任何异常:

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

这是因为在应用 #:: 时,尾部是按名称传递的,因此不会急切地评估它,而是仅在需要时才进行评估(参见 Stream.scala 中的 ConsWrapper.# ::const.apply 和类 Cons 了解更多信息细节)。 另一方面,head 是按值传递的,这意味着它总是会被急切地评估,无论如何(正如 Senthil 所提到的)。这意味着执行以下操作实际上会引发 ArithmeticException:

(1/0) #:: Stream.empty

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

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

【讨论】:

【参考方案2】:

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, ?)

【讨论】:

@Senthil 在他的回答中提出了一个很好的观点,即始终热切地评估 Stream 的头部,因此如果您将除零代码移至尾部,它将起作用:1 #:: (1/0) #:: Stream.empty。但是,如果您调用 Stream 工厂方法,那么它仍然会因为我上面解释的相同原因而中断。 说语言要求在调用方法之前评估参数是非常误导的。按值参数是正确的,但 scala 具有按名称参数。允许将#:: 方法实现为某种惰性运算符的功能。碰巧Stream.apply 有可变参数,这些参数必须按值传递(因此如您所解释的那样在调用之前进行评估) @RégisJean-Gilles - 该语言在调用函数之前仍会评估所有参数,因为它是底层 JVM 的要求——甚至是名称参数。不同之处在于,按名称参数包装在 Lambda 中,并且 Lambda 的主体在您调用它之前不会被评估。 不正确。 JVM 内部发生的事情与语言的语义不同。按名称参数在底层实现为 Function0,但 => T 在语言级别上仍然与 () => T 不同。 事实上=> T甚至不是一流的类型,而() => T是。【参考方案3】:

Stream 将评估头部,而剩余的尾部将延迟评估。在您的示例中,两个流都只有头部,因此会出错。

【讨论】:

以上是关于Scala:流不懒惰?的主要内容,如果未能解决你的问题,请参考以下文章

浅谈正则表达式匹配模式—懒惰模式

懒惰的 Kotlin 抛出 NullPointerException

懒惰程序员的百宝箱:提升工作效率的七大神器

任务 == 懒惰吗?

懒惰急躁和傲慢

Spark Transformation - 为啥它是懒惰的,有啥优势?