Jump to content

Closure (computer programming)

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Chadloder (talk | contribs) at 21:06, 30 September 2006 (Programming languages with closures). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In programming languages, a closure is a function that refers to free variables in its lexical context.

A closure typically comes about when one function is declared entirely within the body of another, and the inner function refers to local variables of the outer function. At runtime, when the outer function executes, a closure is formed. It consists of the inner function's code and references to any variables in the outer function's scope that the closure needs.

Closures are commonly used in functional programming to defer calculation, to hide state, and as arguments to higher-order functions.

A closure combines the code of a function with a special lexical environment bound to that function (scope). Closure lexical variables differ from global variables in that they do not occupy the global variable namespace. They differ from object oriented member variables in that they are bound to function invocations, not object instances.

Implementation and theory

Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function's lexical environment (i.e., the set of available variables and their values) at the time when the function was created.

Closures are closely related to Actors in the Actor model of concurrent computation where the values in the function's lexical environment are called acquaintances. An important issue for closures in concurrent programming languages is whether the variables in a closure can be updated and if so how these updates can be synchronized. Actors provide one solution (Will Clinger 1981).

Examples

Closures typically appear in languages in which functions are first-class values—in other words, such languages allow functions to be passed as arguments, returned from function calls, bound to variable names, etc., just like simpler types such as strings and integers.

For example, consider the following Lisp function:

 (defun best-selling-books (threshold)
   "Return a list of all books with at least THRESHOLD copies sold."
   (filter
     #'(lambda (book) (>= (book-sales book) threshold))
     *book-list*))

Here the lambda expression (lambda (book) (>= (book-sales book) threshold)) appears within the function best-selling-books. When the lambda expression is evaluated, Lisp creates a closure consisting of the code for the lambda and a reference to the threshold variable, which the lambda uses.

The closure is then passed to the filter function, which calls it repeatedly to determine which books are to be added to the result list and which are to be discarded. Because the closure itself has a reference to threshold, it can use that variable each time filter calls it. filter might be defined in a completely separate file.

A function may create a closure and return it. The following example is a function that returns a function.

 (defun derivative (f dx)
   "Return a function that approximates the derivative of f
    using an interval of dx, which should be appropriately small."
   #'(lambda (x) (/ (- (funcall f (+ x dx)) (funcall f x))
                    dx)))

Because the closure in this case outlives the scope of the function that creates it, the variables f and dx live on after the function derivative returns. In languages without closures, the lifetime of a local variable coincides with the execution of the scope where that variable is declared. In languages with closures, variables continue to exist as long as any existing closures have references to them.

Uses of closures

Closures have many uses:

  • Designers of software libraries can allow users to customize behavior by passing closures as arguments to important functions. For example, a function that sorts values can accept a closure argument that compares the values to be sorted according to a user-defined criterion.
  • Because closures delay evaluation—i.e., they do not "do" anything until they are called—they can be used to define control structures. For example, all Smalltalk's standard control structures, including branches (if/then/else) and loops (while and for), are defined using objects whose methods accept closures. Users can easily define their own control structures as well.
  • Multiple functions can be produced which close over the same environment, enabling them to communicate privately by altering that environment.
  • Closures are sometimes better alternatives to objects.

Note: Some speakers call any data structure that binds a lexical environment a closure, but the term usually refers specifically to functions.

Programming languages with closures

Scheme was the first programming language to have fully general, lexically scoped closures. Virtually all functional programming languages, as well as the Smalltalk-descended object-oriented programming languages, support some form of closures.

Among other programming languages with closures are: Groovy, ECMAScript (including JavaScript), Perl, Ruby, Lua, Nice and Pike.

The only statically-typed mainstream programming language to have full-featured closures is C# 2.0, where they are called "anonymous methods". [1]

Though semantics vary greatly, many modern, general-purpose programming languages have lexical scoping and some variation on closures.

Closure-like constructs in other languages

In C, libraries that support callbacks sometimes allow a callback to be registered using two values: a function pointer and a separate void * pointer to arbitrary data of the user's choice. Each time the library executes the callback function, it passes in the data pointer. This allows the callback to maintain state and to refer to information captured at the time it was registered. The idiom is similar to closures in functionality, but not in syntax.

Several object-oriented techniques and language features simulate some features of closures. For example:

  • Eiffel includes inline agents defining closures. An inline agent is an object representing a routine, defined by giving the code of the routine in line. For example, in
  OK_button.click_event.subscribe (
     agent (x, y: INTEGER) do country := map.country_at_coordinates (x, y) ; country.display end
     )

the argument to `subscribe' is an agent, representing a procedure with two arguments; the procedure finds the country at the corresponding coordinates and displays it. The whole agent is `subscribed' to the event type `click_event' for a certain button, so that whenever an instance of the event type occurs on that button — because a user has clicked the button — the procedure will be executed with the mouse coordinates being passed as arguments for x and y.

The main limitation of Eiffel agents, which distinguishes them from true closures, is that they cannot reference local variables from enclosing scope. Only Current (a reference to current object, analogous to this in Java), its features, and arguments of the agent itself can be accessed from within the agent body.

  • C++ allows defining function objects by overloading operator(). These objects behave somewhat like functions in a functional programming language. They may be created at runtime and may contain state. But, they do not implicitly capture local variables as closures do. Two proposals to introduce C++ language support for closures (both proposals call them lambda functions) are being considered by the C++ Standards Committee [2], [3]. The main difference between these proposals is that one stores a copy of all the local variables in a closure by default, and another stores references to original variables. Both provide functionality to override the default behaviour. If some form of these proposals is accepted, one would be able to write
 void foo(string myname) {
   typedef vector<string> names;
   int y;
   names n;
   // ...
   names::iterator i =
     find_if(n.begin(), n.end(), <>(const string& s){return s != myname && s.size() > y;});
   // i is now either n.end() or points to the first string in n
   // which is not equal to  myname and which length is greater than y
 }
  • Java allows defining "anonymous classes" inside a method; an anonymous class may refer to names in lexically enclosing classes, or final (i.e., read-only) variables in the lexically enclosing method.
 class CalculationWindow extends JFrame {
     private JButton btnSave;
     ...
 
     public final void calculateInSeparateThread(final URI uri) {
         // The expression "new Runnable() { ... }" is an anonymous class.
         Runnable runner = new Runnable() {
                 void run() {
                     // It can access final local variables:
                     calculate(uri);
                     // It can access private fields of the enclosing class:
                     // Always update the Graphic components into the Swing Thread
                     SwingUtilities.invokeLater(new Runnable() {
                          public void run() {
                          btnSave.setEnabled(true);
                          }
                     });                   
                 }
             };
         new Thread(runner).start();
     }
 }

Note that closures can be emulated that way by using a final reference to a single-element array. The inner class will not be able to change the value of the reference itself, but it will be able to change the value of the element in the array referenced. This technique is not limited to Java, and will work in any other language with similar restrictions, e.g. Python.

Implementation

A language implementation cannot easily support full closures if its run-time memory model allocates all local variables on a linear stack. In such languages, a function's local variables are deallocated when the function returns. However, a closure requires that the free variables it references survive the enclosing function's execution. Therefore those variables must be allocated so that they persist until no longer needed. This explains why typically languages that natively support closures use garbage collection.

A typical modern Scheme implementation allocates local variables that might be used by closures dynamically and stores all other local variables on the stack.

See also

Reference