Understanding Closures and Context #
In the previous post, we looked at how anonymous functions can simplify code structure, but we skirted around their most powerful capability. An anonymous function can hold additional data, beyond what it gets from its arguments, and it can use that data in its work.
By holding extra data, an anonymous function can be custom-designed, on the fly, to perform a specific duty; it can apply special logic it normally couldn’t have access to; it can access application state; it can even “transform” other functions, changing their behavior, without needing access to their internals, through a sneaky substitution known as “wrapping”; but before we can put closures to work, we need to understand what makes them tick.
This article was written in spring 2012, but every word still holds true, and it applies to JavaScript with very minimal mental translation.
Portable Scope #
Let’s start with a normal, everyday function, defined in a class.
/* figure 01 */
public class Student {
public var name:String;
public var grade:String;
public var tests:Array;
public function toString():String {
return name + ", grade " + grade;
}
public function averageScore():Number {
var connection:Connection = Database.getConnection();
// in real life, we'd do this all in SQL, of course
function retrieveGrade(test:Test):Number {
return connection.getGrade(test);
}
var total:Number = 0;
for each (var test:Test in tests) {
total += retrieveGrade(test);
}
return total / tests.length;
}
}
It should not be surprising that toString
has access to the name
and grade
variables, because those variables are defined in the same scope as the
toString
function. And in averageScore
, it should not be surprising that
retrieveGrade
has access to connection
, because that variable is defined in
the same scope as retrieveGrade
. But how about this?
/* figure 02 */
function nameGenerator():Function {
var names:Array = ["Squirtle", "Bulbasaur", "Charmander", "Pikachu"];
var next:Function = function():String {
var result:String = names.shift();
names.push(result);
return result;
};
return next;
}
var nextName:Function = nameGenerator();
trace(nextName()); // Squirtle
trace(nextName()); // Bulbasaur
In nameGenerator
, the names
array is defined; then the next
function is
defined; then the next
function is returned… and then the names
array
goes out of scope. As far as outside code is concerned, everything that
happens within nameGenerator
happens in a sealed environment. Variables
defined there cannot spill out of it. And this is true: but a function defined
within a function takes that scope with it. When the next
function is
created, it gains access to the variables which were visible at that moment…
and keeps that access as long as it exists.
A Little Context #
Imagine a very simple class definition:
/* figure 03 */
public class Driver {
public var id:String;
public var license:DriversLicense;
public function canLegallyDrive():Boolean {
return license.valid;
}
}
var driver:Driver = Database.getRandomDriver();
trace("License number " + driver.license.id);
trace("Can drive? " + driver.canLegallyDrive());
Outside the class definition, we need to specify driver.license
, but within
the class definition, we can refer to the license
variable directly. That’s
because license
is part of the context in which canLegallyDrive
executes:
that is, license
is a property of this
. Every single line of code which
executes in AS3 has a this
value, even if it is never directly referenced.
Every identifier—every function name, every variable name—is a
property of this
. As I discussed in the last post, the value of this
will
change if the function is executed in a different scope… unless the function
is a closure. Closures take their context—their this
value—with
them. A closure can have access to as many variables as the application
requires, and good taste permits.
Closures are wonderfully abusable. Just wait.
Common Closure Pitfalls #
When working with many UI objects at once, everyone makes this error at least once.
/* figure 04 */
var paintings:Array = Database.getGalleryData().paintings;
for each (var painting:Painting in paintings) {
// PaintingButton constructor draws all UI graphics
var paintingButton:Button = new PaintingButton(painting);
paintingButton.addEventListener(MouseEvent.CLICK,
function(event:Event):void {
showPainting(paintingButton.painting);
});
}
Looks straightforward, right? But when you run it, you’ll find that it always
acts as though you clicked on the last button in the list. This code creates a
separate handler function for each button, but each function is created in the
same scope; it has the same context; its this
value is the same; and so each
function has a reference to the same paintingButton
variable. A reference to
the variable, not a copy of its value. As the loop continues, the value of
paintingButton
changes; and whenever any of the handler functions are called,
they access the current value of paintingButton
.
There are two ways to handle this situation. If you want to use anonymous functions, you will need to create an intermediate variable for each one, like this:
/* figure 05 */
for each (var painting:Painting in paintings) {
var paintingButton:Button = new PaintingButton(painting);
paintingButton.addEventListener(MouseEvent.CLICK,
(function(paintingButton:PaintingButton):Function {
return function(event:Event):void {
showPainting(paintingButton.painting);
}
)(paintingButton));
If this looks crazy to you, you’re not alone. I would not put this in production code, just because it’s a really odd construction. But understanding it is crucial to understanding closures. Let’s break it down a step at a time:
/* figure 06 */
function createHandler(paintingButton:PaintingButton):Function {
var handler:Function = function(event:Event):void {
showPainting(paintingButton.painting);
};
return handler;
}
This is a function which takes a PaintingButton
argument, and returns an
anonymous function which holds a reference to the argument variable. Here’s the
critical part: when we call createHandler(monaLisaButton)
—that is, we
call createHandler
with a specific PaintingButton
—the argument is
assigned to the argument variable paintingButton
for as long as
createHandler
continues to execute. That argument variable is not the same
variable as monaLisaButton
. When the inner function, handler
, is created, it
holds a reference to paintingButton
, whose value has been set to the argument.
It keeps that reference… even though createHandler
completes and the
argument variable otherwise goes out of scope. Since each invocation of
createHandler
creates a new short-lived argument variable, each function
returned from createHandler
has a different context—a different
this
—and thus a different paintingButton
variable which can hold a
different value.
Armed with that knowledge, this code should make a little more sense:
/* figure 07 */
var monaLisaButton:PaintingButton = new PaintingButton(monaLisa);
var handler:Function = (function(paintingButton:PaintingButton):Function {
return function(event:Event):void {
showPainting(paintingButton.painting);
};
})(monaLisaButton);
monaLisaButton.addEventListener(MouseEvent.CLICK, handler);
The value of handler
is derived by declaring a function literal and
immediately executing it. We don’t store the function; we just invoke it
instantly, by tacking (monaLisaButton)
on right after the function literal.
The parentheses around the function literal are not necessary; they’re just a
reminder to consider the function literal as an expression to be evaluated.
These two constructions are equivalent:
/* figure 08 */
var one:int = function():int {return 1;}(); // execute immediately
var two:int = (function():int {return 2;})(); // a bit clearer?
You should be able to understand exactly why figure 05 works now, but in practice, it’s a clear-cut case of “closure abuse”. Here’s a better approach:
/* figure 09 */
for each (var painting:Painting in paintings) {
var paintingButton:Button = new PaintingButton(painting);
paintingButton.addEventListener(MouseEvent.CLICK,
function(event:Event):void {
showPainting(PaintingButton(event.currentTarget).painting);
});
}
Much more pragmatic, much easier to think about. Like any programming tool, closures should only be used where the benefits are worth the extra effort.
Function Binding #
As discussed in the previous post, anonymous functions normally execute in the context in which they are called, not the context in which they are defined. True enough—unless you “bind” the functions. Here’s the original example:
/* figure 10 */
public class Foo {
public var name:String = "Foo";
public var sayName:Function = function():void {
trace(this.name);
}
}
public class Bar {
public var name:String = "Bar";
public var sayName:Function = null;
}
var foo:Foo = new Foo();
var bar:Bar = new Bar();
bar.sayName = foo.sayName;
foo.sayName(); // traces "Foo"
bar.sayName(); // traces "Bar"
And here’s one in which the sayName
function is bound to the instance of
Foo
:
public class Foo {
public var name:String = "Foo";
public var sayName:Function = bind(function():void {
trace(this.name);
});
private function bind(f:Function):Function {
var self:Foo = this;
return function(...args):* {
return f.apply(self, args);
};
}
}
public class Bar {
public var name:String = "Bar";
public var sayName:Function = null;
}
var foo:Foo = new Foo();
var bar:Bar = new Bar();
bar.sayName = foo.sayName;
foo.sayName(); // traces "Foo"
bar.sayName(); // also traces "Foo"
Every function is an instance of Function
; and Function
has two methods,
call
and apply
. They’re both similar in that they let the user specify the
value of this
for a specific invocation of the function. Foo.bind
returns a
function—a closure—which always calls the supplied method with the
value of this
at the time bind
was executed. By making this
immutable, it
makes it safe to pass the function around without changing its behavior.
This is exactly what the function
keyword does, quietly and modestly, whenever
you compile a SWF. To get the same behavior much more easily, simply do this:
/* figure 12 */
public class Foo {
public var name:String = "Foo";
public function sayName():String {
trace(this.name);
}
}
Honestly, function binding per se won’t come up very much in AS3. Most functions are defined within classes, or can take advantage of normal scope rules and the closure mechanism to maintain access to the variables they need. But the core idea of a function which transforms other functions will come up time and again, not only in AS3 but in JavaScript, Ruby, Python, and other languages.
The Potential of Closures #
In the next post, we’ll put closures to work, from simple list manipulation to rewriting application behavior on the fly; and with any luck, you will begin to see just what potential lies buried within these concepts.