Scala中递归定义的lazy val(Stream[T])如何求值?
Scala递归lazy val与Stream的求值机制解析
核心原因拆解
你的困惑来自对Stream的惰性缓存特性和递归lazy val的初始化逻辑的误解,下面一步步拆解执行过程:
代码执行的详细步骤
当运行incrementStream.take(3).toList时,实际执行流程如下:
- 首次调用
incrementStream时,lazy val n并未立即初始化(lazy val的特性:首次被引用时才触发初始化)。 - 当需要获取第一个元素时,
n开始初始化:1 #:: (n map increment1)。这里#::构造的Stream,头部是立即值1,尾部是一个未计算的惰性结构(Stream的map操作本身也是惰性的,不会立即执行)。 - 取第一个元素:直接返回1,无需调用
increment1。 - 取第二个元素:此时需要计算Stream的尾部(即
n map increment1的头部)。此时n已经完成初始化,指向同一个Stream实例,所以取n的头部(1)应用increment1,执行一次并打印increment: 1, 2,得到结果2。这个结果会被缓存——既是n map increment1的头部,同时也是n自己的第二个元素(因为n的定义就是1 #:: 尾部,尾部的头部就是n的第二个元素)。 - 取第三个元素:需要计算
n map increment1的尾部的头部,也就是对n的第二个元素(已缓存的2)应用increment1,执行一次并打印increment: 2, 3,得到结果3。
为什么increment1(1)只执行一次?
你误以为计算n的第二个和第三个元素时都需要用到原始的1,但实际上:
- Stream的每个元素一旦计算完成就会被缓存,不会重复计算。
- 递归的
lazy val n只会初始化一次,所有对n的引用都是同一个Stream实例。当n的第二个元素被计算为2后,后续依赖n的元素都会直接使用这个缓存的2,而不会再回到原始的1重新计算。
关键特性总结
- lazy val:仅在首次被引用时初始化,初始化后所有引用指向同一个实例,不会重复初始化。
- Stream:元素采用惰性计算,每个元素计算完成后会被缓存,后续访问直接使用缓存值,无需重复计算。
内容的提问来源于stack exchange,提问作者utyde
相关产品推荐
相关产品推荐

