Week 7 Code Snippets

Author: Stefan Mitsch

Scope, Lifetime, and Closures

This walkthrough uses three small Scala examples to show how closures capture variables from their defining environment. We move from simple partial application to stateful closures, then to a practical higher-order function pattern (memoization).

1) Closure from a local function (partial application style)

A function returns another function that remembers the greeting prefix s.

def greeter(s: String) : String=>String =
  def g(t: String) : String =
    s"$s, $t!"
  end g
  g
end greeter

val greet = greeter("Hello")
println(greet("Alice")) // Hello, Alice!
println(greet("Bob"))   // Hello, Bob!

The returned function g outlives its definition site, but still has access to s. Next, we use the same mechanism to preserve evolving state across calls.

2) Stateful closure: cumulative average

The function cumulativeAvg creates local variables that are captured by the returned function. Each call updates shared state and computes the running average.

def cumulativeAvg() : Int=>Int =
  var sum = 0
  var count = -1

  def g(x: Int) : Int =
    sum += x
    count += 1
    sum / count
  end g

  count = count + 1
  g
end cumulativeAvg

val cavg = cumulativeAvg()
cavg(2) // returns 2
cavg(4) // returns 3
cavg(6) // returns 4

This is a closure with mutable captured variables (sum, count), illustrating scope and lifetime directly. Now we apply closures to performance optimization.

3) Decorator pattern with memoization

We first define a function with an observable effect (println) and then wrap it with a cache.

def square(x: Int) : Int =
  println(s"$x^2")
  x*x
end square

def memoize[X,Y](fn: X=>Y) : X=>Y =
  val cache = scala.collection.mutable.Map.empty[X,Y]
  x => cache.getOrElseUpdate(x, fn(x))
end memoize

val cachedSquare = memoize(square)
cachedSquare(5)
cachedSquare(5)

On the first call, square(5) is computed and printed. On the second call, the value is returned from the captured cache, so the expensive work is skipped.

These examples connect the following themes: closures preserve access to lexical scope, can maintain state across calls, and enable reusable higher-order abstractions.