Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala Stream

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:

  1. 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.

  2. 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.

  3. Accessing Elements: Like List, you can access elements using methods like head and tail.

    println(naturals.head) // 1
    println(naturals.tail.head) // 2
    
  4. 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)
    
  5. 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.

  6. 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.

  1. 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
    
  2. Creating and Using Streams in Scala:

    Define a Stream and perform operations.

    val stream = Stream(1, 2, 3)
    val doubledStream = stream.map(_ * 2)
    
  3. Infinite Streams in Scala:

    Streams can represent infinite sequences.

    val infiniteStream = Stream.from(1)
    
  4. 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)
    
  5. 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)
    
  6. Memoization in Scala Streams:

    Memoization caches computed values to avoid redundant computations.

    val memoizedStream = infiniteStream.take(10).force
    
  7. 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")
    }
    
  8. 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)