CSC447
Concepts of Programming Languages
Closure
Instructor: James Riely
Closures and Mutability
-
Recall
javac
requires final i
from enclosing scope
| for (int i = 0; i < 5; i++) { |
| new Thread (new Runnable () { |
| public void run () { |
| while (true) { System.out.print (i); } |
| } |
| }).start (); |
| } |
-
So a copy is made
| for (int i = 0; i < 5; i++) { |
| int x = i; |
| new Thread (new Runnable () { |
| public void run () { |
| while (true) { System.out.print (x); } |
| } |
| }).start (); |
| } |
Closures and Mutability
-
The same holds for lambda expressions
| for (int i = 0; i < 5; i++) { |
| new Thread (() -> { |
| while (true) { System.out.print (i); } |
| }).start (); |
| } |
-
So a copy is made
| for (int i = 0; i < 5; i++) { |
| int x = i; |
| new Thread (() -> { |
| while (true) { System.out.print (x); } |
| }).start (); |
| } |
Why is this tricky?
-
enclosing function
outer
is called
-
outer
returns nested function inner
-
inner references
x
from outer
's AR
-
lifetime of
outer
's AR and x
ends
-
nested function
inner
is called
def outer (x:A) : B=>C =
def inner (y:B) : C =
inner
Implementation: Closures
-
Closures store inner function and environment
-
Environment contains variables from enclosing scope
-
Lifetime of environment = lifetime of inner function
-
environment is allocated on the heap
-
Different implementations in different PLs
-
Recurring implementation choice: copy or share?
Implementation Choice
-
Closure contains
-
pointer/reference to code for
inner
-
a copy of
x
def outer (x:A) : B=>C =
def inner (y:B) : C =
...use x and y...
inner
Implementation Choice
-
Closure contains
-
pointer/reference to code for
inner
-
copies of
x
and u
-
inner
sees updated u
?
-
Require
u
to be immutable?
def outer (x:A) : B=>C =
var u:A = x
def inner (y:B) : C =
u = u + 1
inner
Implementation Choice
-
Alternatively, share
u
-
Closure contains
-
pointer/reference to code for
inner
-
copy of
x
-
reference to shared
u
(on heap)
def outer (x:A) : B=>C =
var u:A = x
def inner (y:B) : C =
u = u + 1
inner
Scala2.12 Implementation
object Closure:
def outer (x:Int) : Boolean=>Int =
def inner (y:Boolean) : Int =
x + (if y then 0 else 1)
inner
$ scalac Closure.scala
$ ls -1 Closure*
Closure$$anonfun$outer$1.class
Closure.class
Closure$.class
Closure.scala
Scala2.12 Implementation
-
The closure is an instance of the second class
-
x
copied into field x$1
$ javap -p Closure
Compiled from "Closure.scala"
public final class Closure {
public static scala.Function1<java.lang.Object, java.lang.Object> outer(int);
}
$ javap -p Closure\$\$anonfun\$outer\$1
Compiled from "Closure.scala"
public final class Closure$$anonfun$outer$1 extends scala.runtime.AbstractFunction1<java.lang.Object, java.lang.Object> {
private final int x$1;
public final int apply(boolean);
public Closure$$anonfun$outer$1(int);
}
(some parts removed)
Scala2.12 Implementation
-
u
is a var
declaration, so is mutable
object Closure:
def outer (x:Int) : Boolean=>Int =
var u:Int = x
def inner (y:Boolean) : Int =
x + u + (if y then 0 else 1)
inner
Scala2.12 Implementation
-
x
copied into field x$1
-
u
shared on heap via reference in field u$1
$ javap -p Closure\$\$anonfun\$outer\$1
Compiled from "Closure.scala"
public final class Closure$$anonfun$outer$1 extends scala.runtime.AbstractFunction1<java.lang.Object, java.lang.Object> {
private final int x$1;
private final scala.runtime.IntRef u$1;
public final int apply(boolean);
public Closure$$anonfun$outer$1(int, scala.runtime.IntRef);
}
(some parts removed)
Further reading
-
There are some great resources about closures in the javascript
world, such as this