Scala Stream交错转换行为疑问:map与filter执行解析
关于Scala Stream中map与filter交错执行及惰性求值的问题解答
先看你给出的代码:
sealed trait Stream[+A]{ def foldRight[B](z: => B)(f: (A, => B) => B): B = this match { case Cons(h,t) => f(h(), t().foldRight(z)(f)) case _ => z } def map[B](f: A => B): Stream[B] = foldRight(Stream.empty[B])((h,t) => {println(s"map h:$h"); Stream.cons(f(h), t)}) def filter(f:A=>Boolean):Stream[A] = foldRight(Stream.empty[A])((h,t) => {println(s"filter h:$h"); if(f(h)) Stream.cons(h,t) else t}) } case object Empty extends Stream[Nothing] case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A] object Stream { def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = { lazy val head = hd lazy val tail = tl Cons(() => head, () => tail) } def empty[A]: Stream[A] = Empty def apply[A](as: A*): Stream[A] = if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*)) } Stream(1,2,3,4,5,6).map(_+10).filter(_%2==0)
执行后输出:
map h:1 filter h:11 map h:2 filter h:12
1. 为何map与filter的输出呈现交错状态?
核心原因是Stream的惰性求值,以及foldRight的参数设计。
咱们看foldRight的定义:第二个参数f的第二个入参是=> B,这是一个惰性参数——只有在需要的时候才会计算。当你链式调用map和filter时,整个过程是按需逐个元素处理的:
- 当需要处理第一个元素时,
map会先计算原Stream的第一个元素(触发map里的println),生成映射后的元素,然后把这个元素传给filter; filter处理这个映射后的元素(触发filter里的println),判断是否符合条件。如果不符合,就会请求map的下一个元素(因为要返回map的tail),这时候才会触发下一个元素的map操作。- 整个流程就是“处理一个元素的map → 处理该元素的filter → 再处理下一个元素的map”,所以输出是交错的。
2. 从Stream创建到产生该行为的全部执行步骤
咱们一步步拆解整个流程(假设你是在Scala REPL中执行这段代码,REPL会尝试求值并显示结果,这是触发计算的关键):
- Stream初始化:
Stream(1,2,3,4,5,6)创建了一个由Cons节点链接的惰性Stream。每个Cons的head和tail都是lazy val,初始时没有计算任何元素,只是保存了计算逻辑。 - 调用map:
map(_+10)返回的是foldRight的表达式,但此时foldRight并没有执行任何实际计算——因为Stream是惰性的,只有当需要结果时才会运行。 - 调用filter:同样,
filter(_%2==0)也是返回foldRight的表达式,依旧没有实际执行。 - 触发求值:REPL尝试显示最终Stream的内容,这会触发对Stream头部元素的求值,启动整个计算链:
- 首先执行
filter的foldRight,它需要处理map返回的Stream的第一个元素; - 这会触发
map的foldRight处理原Stream的第一个元素:调用h()获取1,执行map里的println("map h:1"),生成Stream.cons(11, t)(t是原Stream tail的map结果,此时还是惰性的); - 这个11被传给
filter的f函数:执行println("filter h:11"),判断11%2==0不成立,所以返回t(也就是map的tail,对应原Stream的2及以后的元素); - 现在
filter需要获取t的内容,触发map的foldRight处理原Stream的第二个元素:调用h()获取2,执行println("map h:2"),生成Stream.cons(12, t); - 12传给
filter的f函数:执行println("filter h:12"),判断12%2==0成立,于是返回Stream.cons(12, t); - 此时REPL已经获取到第一个符合条件的元素12,默认停止继续求值(因为Stream是惰性的,REPL不会主动遍历所有元素),所以流程到此结束。
- 首先执行
3. 符合filter条件的其他元素(如4、6)为何未出现在输出中?
还是因为惰性求值:Stream不会提前计算所有元素,只有当你明确请求后续元素时,才会触发对应的map和filter操作。
在你的场景中,REPL只需要获取到第一个符合条件的元素就停止了,没有继续请求后续元素,所以原Stream中3、4、5、6对应的map和filter都没有执行。如果你强制遍历所有元素(比如把结果转成List):
Stream(1,2,3,4,5,6).map(_+10).filter(_%2==0).toList
你就会看到所有符合条件的元素对应的输出,比如map h:3、filter h:13、map h:4、filter h:14等等。
内容的提问来源于stack exchange,提问作者Giorgio
相关产品推荐
相关产品推荐

