Skip to content
AgileLogics
4 min read

Lazy Values, Views, and LazyList

Prerequisites: Collection operations

lazy val

A lazy val is computed the first time it's used, then cached:

object Settings:
  lazy val config: Map[String, String] =
    println("loading config...")
    Map("mode" -> "production")

@main def lazyVals(): Unit =
  println("start")
  println(Settings.config("mode"))
  println(Settings.config("mode"))
start
loading config...
production
production

Useful for expensive values that might not be needed.

Views

Collection operations normally build a new collection at each step. A view runs them lazily, element by element, only as far as needed:

@main def views(): Unit =
  var checked = 0
  val firstBig = (1 to 1_000_000).view
    .map { n => checked += 1; n * n }
    .find(_ > 50)
  println(firstBig)
  println(s"checked $checked elements")
Some(64)
checked 8 elements

Without .view, map would square all million numbers first.

LazyList: infinite sequences

@main def lazyLists(): Unit =
  val naturals = LazyList.from(1)
  println(naturals.filter(_ % 7 == 0).take(3).toList)

  lazy val fibs: LazyList[BigInt] =
    BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map(_ + _)
  println(fibs.take(12).toList)
  println(fibs(90))
List(7, 14, 21)
List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89)
2880067194370816120

A LazyList computes elements only when asked and remembers them. The Fibonacci sequence is defined in terms of itself (declaring it as a lazy val is what allows a local value to refer to itself) — only the elements you request are ever computed.