liveBook · Manning

Thunk

As explained in Functional Programming in Scala, Second Edition

A thunk is a zero-argument function representing an unevaluated expression that can be invoked to obtain its result.

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

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Explains explicit and by-name forms, repeated forcing, and memoization, then applies them to branching and lazy lists.
Examples
Demonstrates if2 branching, repeated maybeTwice evaluation, and LazyList head-tail construction.
What it assumes
Assumes basic Scala functions and types while teaching explicit zero-argument functions, by-name parameters, and lazy evaluation.
What you can do after
Apply it via Scala functions and lazy data structures to defer, selectively force, or memoize computations.
How it connects
Connects thunks with nonstrict functions, laziness, LazyList, memoization, and smart constructors.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.