Scala Tutorial
Basics
Control Statements
OOP Concepts
Parameterized - Type
Exceptions
Scala Annotation
Methods
String
Scala Packages
Scala Trait
Collections
Scala Options
Miscellaneous Topics
Scala's Stream
is a data structure that represents a lazy sequence of values. Prior to Scala 2.13, Stream
was the canonical way to represent lazy sequences. Starting with Scala 2.13, Stream
was deprecated in favor of LazyList
.
Stream
(and LazyList
) is similar to List
but has the difference that its elements are computed lazily. This means that the computation of a value in the stream is deferred until it is actually needed. This can be particularly useful when dealing with potentially infinite sequences or sequences that are expensive to compute.
Here's a brief overview of Stream
:
Creating a Stream: You can create a Stream
in various ways.
val s = 1 #:: 2 #:: 3 #:: Stream.empty
This creates a Stream
with elements 1, 2, and 3. The #::
operator is used to prepend an element to a Stream
.
Laziness: A common usage is generating infinite sequences.
def from(n: Int): Stream[Int] = n #:: from(n + 1) val naturals = from(1)
naturals
is an infinite stream of natural numbers. However, only the numbers that are accessed will be computed.
Accessing Elements: Like List
, you can access elements using methods like head
and tail
.
println(naturals.head) // 1 println(naturals.tail.head) // 2
Transformations: You can use familiar methods like map
, filter
, take
, etc. with Stream
.
val evenNumbers = naturals.filter(_ % 2 == 0) println(evenNumbers.take(5).toList) // List(2, 4, 6, 8, 10)
Memory Considerations: Since Stream
caches computed values (to avoid recomputation), holding a reference to the head of the Stream
can cause a memory leak. If you're traversing a large or infinite Stream
and don't need to revisit the elements, it's better to avoid holding a reference to its head.
Transition to LazyList: Starting with Scala 2.13, as mentioned earlier, Stream
has been deprecated in favor of LazyList
. They serve similar purposes, but LazyList
provides clearer semantics in terms of evaluation and offers better overall performance.
If you're working with Scala 2.13 or later, you should prefer using LazyList
over Stream
. However, the concepts of lazy evaluation, infinite sequences, and the benefits they provide remain largely the same across both data structures.
Lazy Evaluation in Scala Stream:
Streams in Scala are lazy collections, meaning their elements are computed only when needed. This is known as lazy evaluation.
val stream = 1 #:: 2 #:: 3 #:: Stream.empty
Creating and Using Streams in Scala:
Define a Stream and perform operations.
val stream = Stream(1, 2, 3) val doubledStream = stream.map(_ * 2)
Infinite Streams in Scala:
Streams can represent infinite sequences.
val infiniteStream = Stream.from(1)
Filtering and Mapping with Scala Streams:
Apply filtering and mapping operations lazily.
val filteredStream = infiniteStream.filter(_ % 2 == 0) val squaredStream = infiniteStream.map(x => x * x)
Scala Stream vs List:
Streams differ from Lists in that they are lazily evaluated and potentially infinite.
val list = List(1, 2, 3) val stream = Stream(1, 2, 3)
Memoization in Scala Streams:
Memoization caches computed values to avoid redundant computations.
val memoizedStream = infiniteStream.take(10).force
Pattern Matching with Scala Streams:
Use pattern matching to destructure Stream elements.
val stream = 1 #:: 2 #:: 3 #:: Stream.empty stream match { case head #:: tail => println(s"Head: $head, Tail: $tail") case _ => println("Empty stream") }
Recursive Algorithms with Scala Streams:
Leverage laziness for recursive algorithms.
def fibonacci(a: Int, b: Int): Stream[Int] = a #:: fibonacci(b, a + b) val fibonacciStream = fibonacci(0, 1).take(10)