CSC 347 - Concepts of Programming Languages

Closures

Instructor: Stefan Mitsch

Learning Objectives

What if functions have free variables?

  • Identify when a function has free variables
  • Identify nested methods and functions
  • Describe the effect of closures: how free variables are bound to variables of the environment

Nested Functions

  • Useful to limit the scope of functions
  • Allowed by GCC, but not C standard
  1. int fact (int n) {
  2. int loop(int n, int result) {
  3. if (n <= 1) {
  4. return result;
  5. } else {
  6. return loop(n - 1, n * result);
  7. }
  8. }
  9. return loop(n, 1);
  10. }
  1. $ gcc -c nested-fact.c
  2. $ gcc -pedantic -c nested-fact.c
  3. function.c: In function ‘fact’:
  4. function.c:2:3: warning: ISO C forbids nested functions [-pedantic]
  • Allowed in Scala
    1. def fact(n:Int) : Int =
    2. def loop(n:Int, result:Int) : Int =
    3. if n<=1 then result
    4. else loop(n-1, n*result)
    5. end loop
    6. loop(n, 1)
    7. end fact

Nested Functions

  • Function f() has free variable x
  • To which x should we bind the free x in f?
  1. val x = 4
  2. def f() = x+1
  3. def g() =
  4. val x = 7
  5. f()
  6. end g
  7. g()
  • Using our simple language semantics: binds x in f to x in line 4 (returns instead of )
  • Simple language semantics does not capture values of variables

  • Simply consults the global store at the time of function application

Nested Functions: Java and Scala

  • Closures: nested functions in Scala and Java store environment on function definition

Scala

  1. def f(x:Int) : Int=>Unit =
  2. val g = (y:Int) => println(s"x=$x, y=$y")
  3. g
  4. end f
  5. val h1 = f(10)
  6. h1(2) // prints x=10, y=2
  7. val h2 = f(20)
  8. h1(3) // prints x=10, y=3
  9. h2(3) // prints x=20, y=3
  • Java
    1. import java.util.function.Function;
    2. public static Function<Integer,Void> f(int x) {
    3. Function<Integer,Void> g = y -> {
    4. System.out.format ("x=%d, y=%d%n", x, y);
    5. return null;
    6. }
    7. return g;
    8. }
    9. public static void main (String[] args) {
    10. Function<Integer,Void> h1 = f(10);
    11. h1.accept(2); // prints x=10, y=2
    12. Function<Integer,Void> h2 = f(20);
    13. h1.accept(3); // prints x=10, y=3
    14. h2.accept(3); // prints x=20, y=3
    15. }

Closures: Problem Summary

  1. def outer(x:A) : B=>C =
  2. def inner(y:B) : C =
  3. //...use x and y...
  4. end inner
  5. inner
  6. end outer
  7. val f = outer(...)
  8. f(...)
  1. Function outer is called
    • AR contains data x
  1. Function outer returns nested function inner
    • Function inner has free variable x
    • ⇒ resolve with x from outer's AR
  2. Lifetime of outer's AR and x ends
  3. Nested function inner is called through alias f
    • Function inner needs x from outer's AR
  • Mutable vs. immutable state?

Closures: Copy or Share

  1. def outer(x:A) : B=>C =
  2. def inner(y:B) : C =
  3. // use x and y
  4. // free: x
  5. // bound: y
  6. end inner
  7. inner
  8. end outer

Closure contains

  • Code for inner
  • Copy of x

Closures: Copy or Share

  1. def outer(x:A) : B=>C =
  2. var u:A = x
  3. def inner(y:B) : C =
  4. // use x, u, and y:
  5. // free: x, u
  6. // bound: y
  7. end inner
  8. u = u + 1
  9. inner
  10. end outer

Closure contains

  • Code for inner
  • Copy of x
  • What to do with u?
    • inner sees updated u?
      • Scala
    • require u to be immutable?
      • Java

Closures: Scala

  • inner has free x
  • Needs closure
  1. def outer(x:Int) : Boolean=>Int =
  2. def inner(y:Boolean) : Int =
  3. x + (if y then 0 else 1)
  4. end inner
  5. inner
  6. end outer
  • Object-oriented implementation in Java

    1. public static Function1<Boolean, Integer> outer(int x) {
    2. return new Closure(x);
    3. }
    4. public final class Closure
    5. extends AbstractFunction1<Boolean, Integer> {
    6. private final int x;
    7. public final Integer apply(Boolean y) {
    8. return x + (y ? 0 : 1);
    9. }
    10. public Closure(int x) { this.x = x; }
    11. }

Closures: Scala

  • inner has free x,u
  • Needs closure
  1. def outer (x:Int) : Boolean=>Int =
  2. var u:Int = x
  3. def inner (y:Boolean) : Int =
  4. x + u + (if y then 0 else 1)
  5. end inner
  6. u = u+1
  7. inner
  8. end outer
  • Object-oriented impl. stores mutable u on heap
    1. public static Function1<Boolean, Integer> outer(int x) {
    2. IntRef u = new IntRef(x);
    3. var c = new Closure(x, u);
    4. u.elem = u.elem+1;
    5. return c;
    6. }
    7. public final class Closure
    8. extends AbstractFunction1<Boolean, Integer> {
    9. private final int x;
    10. private final IntRef u;
    11. public final Integer apply(Boolean y) {
    12. return x + u.elem + (y ? 0 : 1);
    13. }
    14. public Closure(int x, IntRef u) {
    15. this.x = x;
    16. this.u = u;
    17. }
    18. }

Example: Use Closures

How to create a "container" for data and functions?

Object-oriented programming: classes

  1. public class Incrementor {
  2. private int i;
  3. public Incrementor(int i) {
  4. this.i = i;
  5. }
  6. public int increment(int x) {
  7. return x+i;
  8. }
  9. }
  10. // use object
  11. Incrementor inc = new Incrementor(2);
  12. inc.increment(4); // returns 6
  13. inc.increment(5); // returns 7

Functional programming

  • Closures
    1. def incrementor(i:Int) : Int=>Int =
    2. (x:Int) => x+i
    3. end incrementor
    4. // use closure
    5. val inc = incrementor(2)
    6. inc(4) // returns 6
    7. inc(5) // returns 7

Example: Interpret Closures

  • Closure for method
    1. val f : ()=>Int =
    2. var x = -1
    3. def g() = { x = x + 1; x }
    4. g
  • Closure for function
    1. val f : ()=>Int =
    2. var x = -1
    3. () => { x = x + 1; x }
  • Initializes:
    • x to -1 when initializing variable f
  • Returns:
    • incremented x on every call f()
      1. scala> f()
      2. res0: Int = 0
      3. scala> f()
      4. res1: Int = 1

Example: Interpret Closures

  • Closure for method
    1. def g(y: Int) : ()=>Int =
    2. var z = y
    3. def h() = { z = z + 1; z }
    4. h
  • Closure for function
    1. val g: Int=>()=>Int =
    2. y =>
    3. var z = y
    4. () => { z = z + 1; z }
  • g(i) returns:

    • functions ()=>Int, their own z set to i

      1. scala> val h1=g(10)
      2. h1: () => Int = $$Lambda$1098/39661414@54d8c20d
      3. scala> val h2=g(20)
      4. h2: () => Int = $$Lambda$1098/39661414@5bc7e78e
  • h1() and h2() return:

    • their own incremented z

      1. scala> h1()
      2. res3: Int = 11
      3. scala> h1()
      4. res4: Int = 12
      5. scala> h2()
      6. res5: Int = 21
      7. scala> h1()
      8. res6: Int = 13

Example: Use Closures

Implement a function printer that adds an initializable string prefix; wanted use:

  1. val p = printer("> ")
  2. println(p("hello"))
  3. println(p("world!"))
  • Return a function for printing that accesses the prefix argument
    1. def printer(prefix: String) : String=>String =
    2. (s: String) => prefix + s

Example: Use Closures

Implement a map data structure from arrays and index access; wanted use:

  1. val (get, put) = map(10)
  2. put(3, "Hello")
  3. put(6, "world!")
  4. println(get(3))
  • Return a tuple of functions that access a shared array

    1. def map(size: Int) : (Int=>String, (Int, String)=>Unit) =
    2. val elems: Array[String] = Array.ofDim(size)
    3. def get(key: Int) =
    4. if key < size then elems(key)
    5. else throw new NoSuchElementException("Unknown key " + key)
    6. def put(key: Int, value: String) =
    7. elems(key) = value
    8. (get, put)
    9. end map

Summary

  • Closures are necessary when functions have free variables
  • Closures bind free variables of a function to variables from the environment
  • Closures align the lifetime of functions and their accessed environment
  • Closures in Javascript

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Nested Functions: GCC - Lifetime problems caused by nested functions <div class="grid grid-cols-2 gap-4"> <div> ```c typedef void (*funcptr) (int); funcptr f (int x) { void g (int y) { printf ("x = %d, y = %d\n", x, y); } g (1); return &g; } int main (void) { funcptr h = f (10); (*h) (2); f (20); (*h) (3); } ``` </div> <div> Unsafe calls may or may not work ```sh $ gcc -std=c99 nested-gcc.c $ ./a.out x = 10, y = 1 <- g(1): safe to call g, with x=10 x = 10, y = 2 <- (*h)(2): unsafe to call h, created with x=10 x = 20, y = 1 <- g(1): safe to call g x = 20, y = 3 <- (*h)(3): unsafe to call h, created with x=10 ``` </div> </div> ---

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Nested Function: Java With explicit object instantiation <div class="text-xl"> ```java import java.util.function.Function; static Function<Integer,Void> f (int x) { Function<Integer,Void> g = new Function<Integer,Void>() { public Void apply(Integer y) { System.out.format ("x = %d, y = %d%n", x, y); return null; } }; g.apply (1); return g; } public static void main (String[] args) { Function<Integer,Void> h = f (10); h.apply (2); f (20); h.apply (3); } ``` </div> ---