Skip to content

Commit 53130d2

Browse files
committed
Make Stream.withFilter.{map,flatMap} run in constant stack space
The included test currently fails because `map` and `flatMap` do not run in constant stack space on a stream returned by `Stream.withFilter`, as I reported here: https://groups.google.com/d/msg/scala-language/WqJR38REXnk/saaSiDdmyqoJ Fix the problem and add a simple testcase. Note that the stack space consumed when producing an element of this stream is proportional to the number of elements failing the test before the next success. The stack space consumed to produce the stream itself is the space needed to produce the first element, that is, is proportional to the number of failures before the first success.
1 parent 8e7f44c commit 53130d2

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

src/library/scala/collection/immutable/Stream.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,22 +479,57 @@ self =>
479479
final class StreamWithFilter(p: A => Boolean) extends WithFilter(p) {
480480

481481
override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Stream[A], B, That]): That = {
482+
def tailMap(coll: Stream[A]): Stream[B] = {
483+
var head: A = null.asInstanceOf[A]
484+
var tail: Stream[A] = coll
485+
while (true) {
486+
if (tail.isEmpty)
487+
return Stream.Empty
488+
head = tail.head
489+
tail = tail.tail
490+
if (p(head))
491+
return cons(f(head), tailMap(tail))
492+
}
493+
throw new RuntimeException()
494+
}
495+
496+
/*
482497
def tailMap = asStream[B](tail withFilter p map f)
483498
if (isStreamBuilder(bf)) asThat(
484499
if (isEmpty) Stream.Empty
485500
else if (p(head)) cons(f(head), tailMap)
486501
else tailMap
502+
//XXX Alternative: what about having a Stream.step constructor?
487503
)
504+
*/
505+
if (isStreamBuilder(bf)) asThat(tailMap(Stream.this))
488506
else super.map(f)(bf)
489507
}
490508

491509
override def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Stream[A], B, That]): That = {
510+
def tailFlatMap(coll: Stream[A]): Stream[B] = {
511+
var head: A = null.asInstanceOf[A]
512+
var tail: Stream[A] = coll
513+
while (true) {
514+
if (tail.isEmpty)
515+
return Stream.Empty
516+
head = tail.head
517+
tail = tail.tail
518+
if (p(head))
519+
return f(head).toStream append tailFlatMap(tail)
520+
}
521+
throw new RuntimeException()
522+
}
523+
524+
/*
492525
def tailFlatMap = asStream[B](tail withFilter p flatMap f)
493526
if (isStreamBuilder(bf)) asThat(
494527
if (isEmpty) Stream.Empty
495528
else if (p(head)) f(head).toStream append tailFlatMap
496529
else tailFlatMap
497530
)
531+
*/
532+
if (isStreamBuilder(bf)) asThat(tailFlatMap(Stream.this))
498533
else super.flatMap(f)(bf)
499534
}
500535

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Stream()
2+
Stream()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
object Test extends App {
2+
//This runs fine.
3+
val resFMap1 = (1 to 10000).toStream filter (_ => false) flatMap (Seq(_))
4+
val resMap1 = (1 to 10000).toStream filter (_ => false) map (_ + 1)
5+
assert(resMap1.isEmpty)
6+
assert(resFMap1.isEmpty)
7+
println(resMap1)
8+
println(resFMap1)
9+
//This will cause a stack overflow
10+
val resFMap2 = (1 to 10000).toStream withFilter (_ => false) flatMap (Seq(_))
11+
val resMap2 = (1 to 10000).toStream withFilter (_ => false) map (_ + 1)
12+
assert(resMap1 == resMap2)
13+
assert(resFMap1 == resFMap2)
14+
}

0 commit comments

Comments
 (0)