I’m trying to create a lazy sequence in Scala using streams and the zipped method. My goal is to generate an infinite sequence where each element depends on combining previous elements.
Here’s my approach:
lazy val sequence: Stream[Int] = 1 #:: 2 #:: (sequence, sequence.tail).zipped.map(_ * _)
When I try to evaluate the first few elements:
sequence take 5 foreach println
1
2
java.lang.StackOverflowError
at scala.collection.mutable.LazyBuilder.<init>(LazyBuilder.scala:25)
at scala.collection.immutable.Stream$StreamBuilder.<init>(Stream.scala:492)
at scala.collection.immutable.Stream$.newBuilder(Stream.scala:483)
It seems like the zipped method causes issues with lazy evaluation in streams. Is there a fundamental problem with using zipped on infinite streams? What would be the right way to achieve this pattern without running into stack overflow issues?
I ran into something similar when building recursive streams. The core issue is that zipped eagerly constructs its internal machinery, which conflicts with the self-referential nature of your stream definition. What happens is that the stream tries to evaluate itself during construction, creating a circular dependency that exhausts the stack. Instead of relying on collection methods, you can define the stream more directly using cons operations. Try this approach: lazy val sequence: Stream[Int] = { def buildNext(prev: Int, curr: Int, rest: Stream[Int]): Stream[Int] = (prev * curr) #:: buildNext(curr, prev * curr, rest.tail) 1 #:: 2 #:: buildNext(1, 2, sequence.drop(2)) }. This way you control exactly when each element gets computed without forcing premature evaluation of the underlying stream structure. The key is avoiding any operations that need to peek ahead or create intermediate collections.
The problem stems from how zipped handles stream evaluation internally. When you call zipped on two streams, it creates an intermediate structure that forces evaluation of elements prematurely, breaking the lazy semantics you need for infinite sequences. I’ve encountered this exact issue before when working with Fibonacci-like sequences. The solution is to avoid zipped entirely and use explicit recursion with pattern matching: def nextElement(s: Stream[Int]): Int = s match { case a #:: b #:: _ => a * b } lazy val sequence: Stream[Int] = { def loop(current: Stream[Int]): Stream[Int] = nextElement(current) #:: loop(current.tail) 1 #:: 2 #:: loop(sequence) }. This approach maintains proper laziness because each element is computed only when needed, without creating intermediate collections that force evaluation. The key insight is that stream operations like zipped weren’t designed with infinite recursive definitions in mind.
the issue is that zipped isn’t lazy enough for this pattern. try using a simple recursive approach instead - lazy val seq: Stream[Int] = 1 #:: 2 #:: seq.zip(seq.tail).map(p => p._1 * p._2) should work better since zip preserves laziness better than zipped.