# Strictness and Laziness

Each function that we defined on List makes one pass on list and produces a new List. If we chain these functions together, we would have to make multiple passes over the list when it might actually be unnecessary.

Non-strictness is a way to "fuse" these intermediary lists together into one pass.

# Strict and Non-Strict Functions

Typically the arguments of a function is evaluated prior to that function being called:

def square(num: Int): Int = num * num

square(6 + 3) // 81
square(throw new Exception("fail!")) // exception

These functions are called strict functions. Non-strict or lazy functions are functions where the arguments are only evaluated when they are used:

def square(num: => Int): Int =
  try num * num
  catch { case e: Exception => 0 }

square(throw new Exception("fail!")) // 0

The => syntax indicates that the argument is lazy. It is actually a function that gets called when the argument is being used:

def square(num: () => Int): Int = num() * num()
def square(num: => Int): Int = num * num // same as above

Caching

These parameters are not cached. This means that num above will be evaluated twice as it is being used twice.

To cache a value we have to add the lazy keyword to our variable:

lazy val arg = num
arg * arg

# Streams

Streams are essentially lazy lists.

sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](head: () => A, tail: () => Stream[A]) extends Stream[A]

object Stream {
  def cons[A](head: => A, tail: => Stream[A]): Stream[A] = {
    lazy val hd = head // cache the head
    lazy val tl = tail // cache the tail
    Cons(() => hd, () => tl)
  }

  def empty[A]: Stream[A] = Empty

  def apply[A](as: A*): Stream[A] =
    if (as.isEmpty) Empty else cons(as.head, apply(as.tail: _*))
}