A thunk is a zero-argument function representing an unevaluated expression. Invoke it with an empty argument list to force evaluation and obtain its result. Thunks let a function decide whether, when, or how often an argument is evaluated. section 5.1
Thunks make evaluation strategy explicit:
- An explicit thunk has type () => A; the function body forces it with argument syntax such as onTrue(). section 5.1
- A by-name parameter has type => A; Scala wraps the caller’s expression in a thunk and evaluates it when the parameter is referenced. section 5.1
- By-name evaluation is not cached by default: referencing a parameter twice can force its expression twice. section 5.1
- A lazy val can wrap a thunk to memoize its first result, so later forces return the cached value. section 5.2.1
Choosing a branch without evaluating the other
def if2[A](cond: Boolean, onTrue: => A, onFalse: => A): A = if cond then onTrue else onFalse if2(false, sys.error("fail"), 3) // returns 3 The failing expression is passed unevaluated, so only the selected branch is forced. The same idea underlies short-circuiting operators and supports separating a computation’s description from its evaluation. section 5.1 section 5.6
Listing 5.2 A simple definition for LazyList
enum LazyList[+A]:
case Empty
case Cons(h: () => A, t: () => LazyList[A]) #1
object LazyList:
def cons[A](
hd: => A, tl: => LazyList[A]
): LazyList[A] = #2
lazy val head = hd #3
lazy val tail = tl
Cons(() => head, () => tail)
def empty[A]: LazyList[A] = Empty #4
def apply[A](as: A*): LazyList[A] = #5
if as.isEmpty then empty
else cons(as.head, apply(as.tail*))
LazyList stores its head and tail as explicit thunks, allowing traversal to force only the demanded portion; its smart constructor memoizes those computations. section 5.2 section 5.2.1
