Scala 斯卡拉:流不懒惰吗?

Scala 斯卡拉:流不懒惰吗?,scala,stream,lazy-evaluation,Scala,Stream,Lazy Evaluation,我知道流应该在Scala中被惰性地评估,但我认为我正遭受某种基本的误解,因为它们似乎比我预期的更急切 在本例中: val initial = Stream(1) lazy val bad = Stream(1/0) println((initial ++ bad) take 1) 我得到一个java.lang.arithmetricException,它似乎是由零除法引起的。我希望bad永远不会得到计算,因为我只从流中请求了一个元素。怎么了?流是惰性的这一事实并没有改变方法参数被急切地求

我知道流应该在Scala中被惰性地评估,但我认为我正遭受某种基本的误解,因为它们似乎比我预期的更急切

在本例中:

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

我得到一个
java.lang.arithmetricException
,它似乎是由零除法引起的。我希望
bad
永远不会得到计算,因为我只从流中请求了一个元素。怎么了?

流是惰性的这一事实并没有改变方法参数被急切地求值的事实

流(1/0)
扩展为
流。应用(1/0)
。该语言的语义要求在调用方法之前对参数进行求值(因为
Stream.apply
方法不使用按名称调用参数),因此它尝试求值
1/0
作为参数传递给
Stream.apply
方法,这会导致算术异常

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

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
这是因为在应用
::
时,尾部是按名称传递的,以便不急于对其求值,而只在需要时才进行求值(有关详细信息,请参见
Stream.scala中的
conswapper.#:::
const.apply
和class
Cons
)。 另一方面,头部是通过值传递的,这意味着无论发生什么情况,它都将被热切地评估(如Senthil所述)。这意味着执行以下操作实际上会引发算术异常:

(1/0) #:: Stream.empty
这是一个值得了解的问题。然而,这不是你面临的问题

在您的例子中,算术异常甚至在实例化单个流之前发生。在
lazy val bad=Stream(1/0)
中调用
Stream.apply
时,会急切地执行该参数,因为它没有声明为按名称参数
Stream.apply实际上接受一个vararg参数,这些参数必须按值传递。

即使它是按名称传递的,
算术异常
也会很快被触发,因为正如前面所说,流的头部总是被提前评估。

@Senthil在他的回答中指出,流的头部总是被热切地评估,因此,如果将零除法代码移到尾部,它将起作用:
1;:(1/0);::Stream.empty
。但是,如果您正在调用
工厂方法,那么它仍然会中断,原因与我上面解释的相同。如果说该语言要求在调用该方法之前对参数进行求值,这是非常误导的。对于按值参数,这是正确的,但scala有按名称参数。允许将
#::
方法作为某种惰性操作符实现的特性。恰好
Stream.apply
有varargs参数,这些参数必须通过值传递(因此在调用之前进行计算,如您所解释的)@RégisJean-Gilles-该语言在调用函数之前仍然会计算所有参数,因为它是底层JVM的一个要求,甚至是通过名称参数。不同之处在于,按名称参数包装在Lambda中,在调用Lambda之前不会计算Lambda的主体。不正确。JVM内部发生的事情与语言的语义不同。by name参数是作为函数0实现的,但是
=>T
在语言级别上仍然与
()=>T
不同。事实上
=>T
甚至不是第一类类型,而
()=>T
是。一般来说,了解流是一件非常好的事情,但这并不是罪魁祸首:
1/0
在调用
流之前进行评估。应用
,甚至在实例化流之前。