Hubbry Logo
Closure (computer programming)Closure (computer programming)Main
Open search
Closure (computer programming)
Community hub
Closure (computer programming)
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Closure (computer programming)
Closure (computer programming)
from Wikipedia

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function[a] together with an environment.[1] The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.[b] Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.

History and etymology

[edit]

The concept of closures was developed in the 1960s for the mechanical evaluation of expressions in the λ-calculus and was first fully implemented in 1970 as a language feature in the PAL programming language to support lexically scoped first-class functions.[2]

Peter Landin defined the term closure in 1964 as having an environment part and a control part as used by his SECD machine for evaluating expressions.[3] Joel Moses credits Landin with introducing the term closure to refer to a lambda expression with open bindings (free variables) that have been closed by (or bound in) the lexical environment, resulting in a closed expression, or closure.[4][5] This use was subsequently adopted by Sussman and Steele when they defined Scheme in 1975,[6] a lexically scoped variant of Lisp, and became widespread.

Sussman and Abelson also use the term closure in the 1980s with a second, unrelated meaning: the property of an operator that adds data to a data structure to also be able to add nested data structures. This use of the term comes from mathematics use, rather than the prior use in computer science. The authors consider this overlap in terminology to be "unfortunate."[7]

Anonymous functions

[edit]

The term closure is often used as a synonym for anonymous function, though strictly, an anonymous function is a function literal without a name, while a closure is an instance of a function, a value, whose non-local variables have been bound either to values or to storage locations (depending on the language; see the lexical environment section below).

For example, in the following Python code:

def f(x: int) -> Callable[[int], int]:
    def g(y: int) -> int:
        return x + y
    return g  # Return a closure.

def h(x: int) -> Callable[[int], int]:
    return lambda y: x + y  # Return a closure.

# Assigning specific closures to variables.
a: Callable[[int], int] = f(1)
b: Callable[[int], int] = h(1)

# Using the closures stored in variables.
assert a(5) == 6
assert b(5) == 6

# Using closures without binding them to variables first.
assert f(1)(5) == 6  # f(1) is the closure.
assert h(1)(5) == 6  # h(1) is the closure.

the values of a and b are closures, in both cases produced by returning a nested function with a free variable from the enclosing function, so that the free variable binds to the value of parameter x of the enclosing function. The closures in a and b are functionally identical. The only difference in implementation is that in the first case we used a nested function with a name, g, while in the second case we used an anonymous nested function (using the Python keyword lambda for creating an anonymous function). The original name, if any, used in defining them is irrelevant.

A closure is a value like any other value. It does not need to be assigned to a variable and can instead be used directly, as shown in the last two lines of the example. This usage may be deemed an "anonymous closure".

The nested function definitions are not themselves closures: they have a free variable which is not yet bound. Only once the enclosing function is evaluated with a value for the parameter is the free variable of the nested function bound, creating a closure, which is then returned from the enclosing function.

Lastly, a closure is only distinct from a function with free variables when outside of the scope of the non-local variables, otherwise the defining environment and the execution environment coincide and there is nothing to distinguish these (static and dynamic binding cannot be distinguished because the names resolve to the same values). For example, in the program below, functions with a free variable x (bound to the non-local variable x with global scope) are executed in the same environment where x is defined, so it is immaterial whether these are actually closures:

x: int = 1
nums: list[int] = [1, 2, 3]

def f(y: int) -> int:
    return x + y

map(f, nums)
map(lambda y: x + y, nums)

This is most often achieved by a function return, since the function must be defined within the scope of the non-local variables, in which case typically its own scope will be smaller.

This can also be achieved by variable shadowing (which reduces the scope of the non-local variable), though this is less common in practice, as it is less useful and shadowing is discouraged. In this example f can be seen to be a closure because x in the body of f is bound to the x in the global namespace, not the x local to g:

x: int = 0

def f(y: int) -> int:
    return x + y

def g(z: int) -> int:
    x: int = 1  # local x shadows global x
    return f(z)

print(g(1))
# prints 1, not 2

Applications

[edit]

The use of closures is associated with languages where functions are first-class objects, in which functions can be returned as results from higher-order functions, or passed as arguments to other function calls; if functions with free variables are first-class, then returning one creates a closure. This includes functional programming languages such as Lisp and ML, and many modern, multi-paradigm languages, such as Julia, Python, and Rust. Closures are also often used with callbacks, particularly for event handlers, such as in JavaScript, where they are used for interactions with a dynamic web page.

Closures can also be used in a continuation-passing style to hide state. Constructs such as objects and control structures can thus be implemented with closures. In some languages, a closure may occur when a function is defined within another function, and the inner function refers to local variables of the outer function. At run-time, when the outer function executes, a closure is formed, consisting of the inner function's code and references (the upvalues) to any variables of the outer function required by the closure.

First-class functions

[edit]

Closures typically appear in languages with first-class functions—in other words, such languages enable 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 Scheme function:

; Return a list of all books with at least THRESHOLD copies sold.
(define (best-selling-books threshold)
  (filter
    (lambda (book)
      (>= (book-sales book) threshold))
    book-list))

In this example, the lambda expression (lambda (book) (>= (book-sales book) threshold)) appears within the function best-selling-books. When the lambda expression is evaluated, Scheme creates a closure consisting of the code for the lambda expression and a reference to the threshold variable, which is a free variable inside the lambda expression.

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 has a reference to threshold, it can use that variable each time filter calls it. The function filter might be defined in a separate file.

Here is the same example rewritten in JavaScript, another popular language with support for closures:

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(book => book.sales >= threshold);
}

The arrow operator => is used to define an arrow function expression, and an Array.filter method[8] instead of a global filter function, but otherwise the structure and the effect of the code are the same.

A function may create a closure and return it, as in this example:

// Return a function that approximates the derivative of f
// using an interval of dx, which should be appropriately small.
function derivative(f, dx) {
  return x => (f(x + dx) - f(x)) / dx;
}

Because the closure in this case outlives the execution of the function that creates it, the variables f and dx live on after the function derivative returns, even though execution has left their scope and they are no longer visible. In languages without closures, the lifetime of an automatic local variable coincides with the execution of the stack frame where that variable is declared. In languages with closures, variables must continue to exist as long as any existing closures have references to them. This is most commonly implemented using some form of garbage collection.

State representation

[edit]

A closure can be used to associate a function with a set of "private" variables, which persist over several invocations of the function. The scope of the variable encompasses only the closed-over function, so it cannot be accessed from other program code. These are analogous to private variables in object-oriented programming, and in fact closures are similar to stateful function objects (or functors) with a single call-operator method.

In stateful languages, closures can thus be used to implement paradigms for state representation and information hiding, since the closure's upvalues (its closed-over variables) are of indefinite extent, so a value established in one invocation remains available in the next. Closures used in this way no longer have referential transparency, and are thus no longer pure functions; nevertheless, they are commonly used in impure functional languages such as Scheme.

Other uses

[edit]

Closures have many uses:

  • 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 of 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 also.
  • In languages which implement assignment, multiple functions can be produced that close over the same environment, enabling them to communicate privately by altering that environment. In Scheme:
(define foo #f)
(define bar #f)

(let ((secret-message "none"))
  (set! foo (lambda (msg) (set! secret-message msg)))
  (set! bar (lambda () secret-message)))

(display (bar)) ; prints "none"
(newline)
(foo "meet me by the docks at midnight")
(display (bar)) ; prints "meet me by the docks at midnight"
  • Closures can be used to implement object systems.[9]

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

Implementation and theory

[edit]

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) at the time when the closure was created. The referencing environment binds the non-local names to the corresponding variables in the lexical environment at the time the closure is created, additionally extending their lifetime to at least as long as the lifetime of the closure. When the closure is entered at a later time, possibly with a different lexical environment, the function is executed with its non-local variables referring to the ones captured by the closure, not the current environment.

A language implementation cannot easily support full closures if its run-time memory model allocates all automatic variables on a linear stack. In such languages, a function's automatic 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, typically via heap allocation, rather than on the stack, and their lifetime must be managed so they survive until all closures referencing them are no longer in use.

This explains why, typically, languages that natively support closures also use garbage collection. The alternatives are manual memory management of non-local variables (explicitly allocating on the heap and freeing when done), or, if using stack allocation, for the language to accept that certain use cases will lead to undefined behaviour, due to dangling pointers to freed automatic variables, as in lambda expressions in C++11[10] or nested functions in GNU C.[11] The funarg problem (or "functional argument" problem) describes the difficulty of implementing functions as first class objects in a stack-based programming language such as C or C++. Similarly in D version 1, it is assumed that the programmer knows what to do with delegates and automatic local variables, as their references will be invalid after return from its definition scope (automatic local variables are on the stack) – this still permits many useful functional patterns, but for complex cases needs explicit heap allocation for variables. D version 2 solved this by detecting which variables must be stored on the heap, and performs automatic allocation. Because D uses garbage collection, in both versions, there is no need to track usage of variables as they are passed.

In strict functional languages with immutable data (e.g. Erlang), it is very easy to implement automatic memory management (garbage collection), as there are no possible cycles in variables' references. For example, in Erlang, all arguments and variables are allocated on the heap, but references to them are additionally stored on the stack. After a function returns, references are still valid. Heap cleaning is done by incremental garbage collector.

In ML, local variables are lexically scoped, and hence define a stack-like model, but since they are bound to values and not to objects, an implementation is free to copy these values into the closure's data structure in a way that is invisible to the programmer.

Scheme, which has an ALGOL-like lexical scope system with dynamic variables and garbage collection, lacks a stack programming model and does not suffer from the limitations of stack-based languages. Closures are expressed naturally in Scheme. The lambda form encloses the code, and the free variables of its environment persist within the program as long as they can possibly be accessed, and so they can be used as freely as any other Scheme expression.[citation needed]

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.[12]

Closures are closely related to function objects; the transformation from the former to the latter is known as defunctionalization or lambda lifting; see also closure conversion.[citation needed]

Differences in semantics

[edit]

Lexical environment

[edit]

As different languages do not always have a common definition of the lexical environment, their definitions of closure may vary also. The commonly held minimalist definition of the lexical environment defines it as a set of all bindings of variables in the scope, and that is also what closures in any language have to capture. However the meaning of a variable binding also differs. In imperative languages, variables bind to relative locations in memory that can store values. Although the relative location of a binding does not change at runtime, the value in the bound location can. In such languages, since closure captures the binding, any operation on the variable, whether done from the closure or not, are performed on the same relative memory location. This is often called capturing the variable "by reference". Here is an example illustrating the concept in ECMAScript, which is one such language:

// Javascript
var f, g;
function foo() {
  var x;
  f = function() { return ++x; };
  g = function() { return --x; };
  x = 1;
  alert('inside foo, call to f(): ' + f());
}
foo();  // 2
alert('call to g(): ' + g());  // 1 (--x)
alert('call to g(): ' + g());  // 0 (--x)
alert('call to f(): ' + f());  // 1 (++x)
alert('call to f(): ' + f());  // 2 (++x)

Function foo and the closures referred to by variables f and g all use the same relative memory location signified by local variable x.

In some instances the above behaviour may be undesirable, and it is necessary to bind a different lexical closure. Again in ECMAScript, this would be done using the Function.bind().

Example 1: Reference to an unbound variable

[edit]

[13]

var module = {
  x: 42,
  getX: function() {return this.x; }
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// emits undefined as 'x' is not specified in global scope.

var boundGetX = unboundGetX.bind(module); // specify object module as the closure
console.log(boundGetX()); // emits 42

Example 2: Accidental reference to a bound variable

[edit]

For this example the expected behaviour would be that each link should emit its id when clicked; but because the variable 'e' is bound to the scope above, and lazy evaluated on click, what actually happens is that each on click event emits the id of the last element in 'elements' bound at the end of the for loop.[14]

var elements = document.getElementsByTagName('a');
// Incorrect: e is bound to the function containing the 'for' loop, not the closure of "handle"
for (var e of elements) { 
  e.onclick = function handle() { 
    alert(e.id);
  } 
}

Again here variable e would need to be bound by the scope of the block using handle.bind(this) or the let keyword.

On the other hand, many functional languages, such as ML, bind variables directly to values. In this case, since there is no way to change the value of the variable once it is bound, there is no need to share the state between closures—they just use the same values. This is often called capturing the variable "by value". Java's local and anonymous classes also fall into this category—they require captured local variables to be final, which also means there is no need to share state.

Some languages enable choosing between capturing the value of a variable or its location. For example, in C++11, captured variables are either declared with [&], which means captured by reference, or with [=], which means captured by value.

Yet another subset, lazy functional languages such as Haskell, bind variables to results of future computations rather than values. Consider this example in Haskell:

-- Haskell
foo :: Fractional a => a -> a -> (a -> a)
foo x y = (\z -> z + r)
          where r = x / y

f :: Fractional a => a -> a
f = foo 1 0

main = print (f 123)

The binding of r captured by the closure defined within function foo is to the computation (x / y)—which in this case results in division by zero. However, since it is the computation that is captured, and not the value, the error only manifests when the closure is invoked, and then attempts to use the captured binding.

Closure leaving

[edit]

Yet more differences manifest themselves in the behavior of other lexically scoped constructs, such as return, break and continue statements. Such constructs can, in general, be considered in terms of invoking an escape continuation established by an enclosing control statement (in case of break and continue, such interpretation requires looping constructs to be considered in terms of recursive function calls). In some languages, such as ECMAScript, return refers to the continuation established by the closure lexically innermost with respect to the statement—thus, a return within a closure transfers control to the code that called it. However, in Smalltalk, the superficially similar operator ^ invokes the escape continuation established for the method invocation, ignoring the escape continuations of any intervening nested closures. The escape continuation of a particular closure can only be invoked in Smalltalk implicitly by reaching the end of the closure's code. These examples in ECMAScript and Smalltalk highlight the difference:

"Smalltalk"
foo
  | xs |
  xs := #(1 2 3 4).
  xs do: [:x | ^x].
  ^0
bar
  Transcript show: (self foo printString) "prints 1"
// ECMAScript
function foo() {
  var xs = [1, 2, 3, 4];
  xs.forEach(function (x) { return x; });
  return 0;
}
alert(foo()); // prints 0

The above code snippets will behave differently because the Smalltalk ^ operator and the JavaScript return operator are not analogous. In the ECMAScript example, return x will leave the inner closure to begin a new iteration of the forEach loop, whereas in the Smalltalk example, ^x will abort the loop and return from the method foo.

Common Lisp provides a construct that can express either of the above actions: Lisp (return-from foo x) behaves as Smalltalk ^x, while Lisp (return-from nil x) behaves as JavaScript return x. Hence, Smalltalk makes it possible for a captured escape continuation to outlive the extent in which it can be successfully invoked. Consider:

"Smalltalk"
foo
    ^[ :x | ^x ]
bar
    | f |
    f := self foo.
    f value: 123 "error!"

When the closure returned by the method foo is invoked, it attempts to return a value from the invocation of foo that created the closure. Since that call has already returned and the Smalltalk method invocation model does not follow the spaghetti stack discipline to facilitate multiple returns, this operation results in an error.

Some languages, such as Ruby, enable the programmer to choose the way return is captured. An example in Ruby:

# Ruby

# Closure using a Proc
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo"
end

# Closure using a lambda
def bar
  f = lambda { return "return from lambda" }
  f.call # control does not leave bar here
  return "return from bar"
end

puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"

Both Proc.new and lambda in this example are ways to create a closure, but semantics of the closures thus created are different with respect to the return statement.

In Scheme, definition and scope of the return control statement is explicit (and only arbitrarily named 'return' for the sake of the example). The following is a direct translation of the Ruby sample.

; Scheme
(define call/cc call-with-current-continuation)

(define (foo)
  (call/cc
   (lambda (return)
     (define (f) (return "return from foo from inside proc"))
     (f) ; control leaves foo here
     (return "return from foo"))))

(define (bar)
  (call/cc
   (lambda (return)
     (define (f) (call/cc (lambda (return) (return "return from lambda"))))
     (f) ; control does not leave bar here
     (return "return from bar"))))

(display (foo)) ; prints "return from foo from inside proc"
(newline)
(display (bar)) ; prints "return from bar"

Closure-like constructs

[edit]

Some languages have features which simulate the behavior of closures. In languages such as C++, C#, D, Java, Objective-C, and Visual Basic (.NET) (VB.NET), these features are the result of the language's object-oriented paradigm.

Callbacks (C)

[edit]

Some C libraries support callbacks. This is sometimes implemented by providing two values when registering the callback with the library: a function pointer and a separate void* pointer to arbitrary data of the user's choice. When the library executes the callback function, it passes along the data pointer. This enables the callback to maintain state and to refer to information captured at the time it was registered with the library. The idiom is similar to closures in functionality, but not in syntax. The void* pointer is not type safe so this C idiom differs from type-safe closures in C#, Haskell or ML.

Callbacks are used extensively in graphical user interface (GUI) widget toolkits to implement event-driven programming by associating general functions of graphical widgets (menus, buttons, check boxes, sliders, spinners, etc.) with application-specific functions implementing the specific desired behavior for the application.

Nested function and function pointer (C)

[edit]

With a GNU Compiler Collection (GCC) extension, a nested function[15] can be used and a function pointer can emulate closures, provided the function does not exit the containing scope. The next example is invalid because adder is a top-level definition (depending on compiler version, it could produce a correct result if compiled with no optimizing, i.e., at -O0):

#include <stdio.h>

typedef int (*FnIntToInt)(int); // type of function int->int

FnIntToInt adder(int number) {
    int add (int value) { return value + number; }
    return &add; // & operator is optional here because the name of a function in C is a pointer pointing on itself
}

int main(void) {
    FnIntToInt add10 = adder(10);
    printf("%d\n", add10(1));
    return 0;
}

But moving adder (and, optionally, the typedef) in main makes it valid:

#include <stdio.h>

typedef int (*FnIntToInt)(int); // type of function int->int

int main(void) { 
    FnIntToInt adder(int number) {
        int add (int value) { 
            return value + number; 
        }
        return add;
    }
  
    FnIntToInt add10 = adder(10);
    printf("%d\n", add10(1));
    return 0;
}

If executed this now prints 11 as expected.

Local classes and lambda functions (Java)

[edit]

Java enables classes to be defined inside methods. These are called local classes. When such classes are not named, they are known as anonymous classes (or anonymous inner classes). A local class (either named or anonymous) may refer to names in lexically enclosing classes, or read-only variables (marked as final) in the lexically enclosing method.

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The expression "new Runnable() { ... }" is an anonymous class implementing the 'Runnable' interface.
        new Thread(
            new Runnable() {
                void run() {
                    // It can read final local variables:
                    calculate(uri);
                    // It can access private fields of the enclosing class:
                    result = result + 10;
                }
            }
        ).start();
    }
}

The capturing of final variables enables capturing variables by value. Even if the variable to capture is non-final, it can always be copied to a temporary final variable just before the class.

Capturing of variables by reference can be emulated by using a final reference to a mutable container, for example, a one-element array. The local class will not be able to change the value of the container reference, but it will be able to change the contents of the container.

With the advent of Java 8's lambda expressions,[16] the closure causes the above code to be executed as:

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The code () -> { /* code */ } is a closure.
        new Thread(() -> {
            calculate(uri);
            result = result + 10;
        }).start();
    }
}

Local classes are one of the types of inner class that are declared within the body of a method. Java also supports inner classes that are declared as non-static members of an enclosing class.[17] They are normally referred to just as "inner classes".[18] These are defined in the body of the enclosing class and have full access to instance variables of the enclosing class. Due to their binding to these instance variables, an inner class may only be instantiated with an explicit binding to an instance of the enclosing class using a special syntax.[19]

public class EnclosingClass {
    /* Define the inner class */
    public class InnerClass {
        public int incrementAndReturnCounter() {
            return counter++;
        }
    }

    private int counter;
    {
        counter = 0;
    }

    public int getCounter() {
        return counter;
    }

    public static void main(String[] args) {
        EnclosingClass enclosingClassInstance = new EnclosingClass();
        /* Instantiate the inner class, with binding to the instance */
        EnclosingClass.InnerClass innerClassInstance =
            enclosingClassInstance.new InnerClass();

        for (int i = enclosingClassInstance.getCounter();
             (i = innerClassInstance.incrementAndReturnCounter()) < 10;
             /* increment step omitted */) {
            System.out.println(i);
        }
    }
}

Upon execution, this will print the integers from 0 to 9. Beware to not confuse this type of class with the nested class, which is declared in the same way with an accompanied usage of the "static" modifier; those have not the desired effect but are instead just classes with no special binding defined in an enclosing class.

As of Java 8, Java supports functions as first class objects. Lambda expressions of this form are considered of type Function<T,U> with T being the domain and U the image type. The expression can be called with its .apply(T t) method, but not with a standard method call.

public static void main(String[] args) {
    Function<String, Integer> length = s -> s.length();

    System.out.println(length.apply("Hello, world!")); // Will print 13.
}

Blocks (C, C++, Objective-C 2.0)

[edit]

Apple introduced blocks, a form of closure, as a nonstandard extension into C, C++, Objective-C 2.0 and in Mac OS X 10.6 "Snow Leopard" and iOS 4.0. Apple made their implementation available for the GCC and clang compilers.

Pointers to block and block literals are marked with ^. Normal local variables are captured by value when the block is created, and are read-only inside the block. Variables to be captured by reference are marked with __block. Blocks that need to persist outside of the scope they are created in may need to be copied.[20][21]

typedef int (^IntBlock)();

IntBlock downCounter(int start) {
	 __block int i = start;
	 return [[ ^int() {
		 return i--;
	 } copy] autorelease];
}

IntBlock f = downCounter(5);
NSLog(@"%d", f());
NSLog(@"%d", f());
NSLog(@"%d", f());

Delegates (C#, VB.NET, D)

[edit]

C# anonymous methods and lambda expressions support closure:

int[] data = new[]{1, 2, 3, 4};
int multiplier = 2;
List<int> result = data.Select(x => x * multiplier);

Visual Basic .NET, which has many language features similar to those of C#, also supports lambda expressions with closures:

Dim data = {1, 2, 3, 4}
Dim multiplier = 2
Dim result = data.Select(Function(x) x * multiplier)

In D, closures are implemented by delegates, a function pointer paired with a context pointer (e.g. a class instance, or a stack frame on the heap in the case of closures).

auto test1() {
    int a = 7;
    return delegate() { return a + 3; }; // anonymous delegate construction
}

auto test2() {
    int a = 20;
    int foo() { return a + 5; } // inner function
    return &foo;  // other way to construct delegate
}

void bar() {
    auto dg = test1();
    dg();    // =10   // ok, test1.a is in a closure and still exists

    dg = test2();
    dg();    // =25   // ok, test2.a is in a closure and still exists
}

D version 1, has limited closure support. For example, the above code will not work correctly, because the variable a is on the stack, and after returning from test(), it is no longer valid to use it (most probably calling foo via dg(), will return a 'random' integer). This can be solved by explicitly allocating the variable 'a' on heap, or using structs or class to store all needed closed variables and construct a delegate from a method implementing the same code. Closures can be passed to other functions, as long as they are only used while the referenced values are still valid (for example calling another function with a closure as a callback parameter), and are useful for writing generic data processing code, so this limitation, in practice, is often not an issue.

This limitation was fixed in D version 2 - the variable 'a' will be automatically allocated on the heap because it is used in the inner function, and a delegate of that function can escape the current scope (via assignment to dg or return). Any other local variables (or arguments) that are not referenced by delegates or that are only referenced by delegates that do not escape the current scope, remain on the stack, which is simpler and faster than heap allocation. The same is true for inner's class methods that reference a function's variables.

Function objects (C++)

[edit]

C++ enables 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. As of the 2011 revision, the C++ language also supports closures, which are a type of function object constructed automatically from a special language construct called lambda-expression. A C++ closure may capture its context either by storing copies of the accessed variables as members of the closure object or by reference. In the latter case, if the closure object escapes the scope of a referenced object, invoking its operator() causes undefined behavior since C++ closures do not extend the lifetime of their context.

import std;

using std::string;
using std::vector;

void foo(std::string& myName) {
    int n = 100;
    vector<string> v;
    // ...

    // i is of type vector<string>::iterator
    auto i = std::ranges::find_if(v, [&](const string& s) -> bool { return s != name && s.size() > n; }); 
    // 'i' is now either 'v.end()' or points to the first string in 'v'
    // which is not equal to 'name' and whose length is greater than 'n'
}

C++ also has the object std::function<R(Args...)> for holding an anonymous function.[22] However, because anonymous functions have compiler-generated names and are distinct, storing one as a std::function uses type erasure which causes minor overhead.

Inline agents (Eiffel)

[edit]

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
		map.country_at_coordinates (x, y).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 closures in other languages, is that they cannot reference local variables from the enclosing scope. This design decision helps in avoiding ambiguity when talking about a local variable value in a closure - should it be the latest value of the variable or the value captured when the agent is created? Only Current (a reference to current object, analogous to this in Java), its features, and arguments of the agent can be accessed from within the agent body. The values of the outer local variables can be passed by providing additional closed operands to the agent.

C++Builder __closure reserved word

[edit]

Embarcadero C++Builder provides the reserved word __closure to provide a pointer to a method with a similar syntax to a function pointer.[23]

Standard C allows writing a typedef for a pointer to a function type using the following syntax:

typedef void (*TMyFunctionPointer)( void );

In a similar way, a typedef can be declared for a pointer to a method using this syntax:

typedef void (__closure *TMyMethodPointer)();

See also

[edit]

Notes

[edit]

References

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
In , a closure is a that pairs a function with an environment recording the bindings of its non-local variables, enabling the function to access those variables from its lexical scope even after the enclosing scope has returned. This mechanism implements lexical scoping in languages supporting first-class functions, where functions can be treated as values that are passed, returned, or stored. The term "closure" originates from Peter J. Landin's 1964 paper, where he defined it as "a bundle of information called a 'closure,' comprising the λ-expression and the environment relative to which it was evaluated." Closures facilitate key programming paradigms, particularly in , by supporting higher-order functions that operate on other functions while preserving context. They enable techniques such as , where functions are partially applied to create specialized variants, and the creation of private state without traditional classes, as seen in emulating object-oriented patterns. Historically, closures gained prominence in the 1970s with the development of Scheme, a of that made them a core feature for lexical scoping and first-class procedures. Today, closures are integral to numerous languages, including Scheme for pure functional computation, for event handling and asynchronous callbacks, for lightweight scripting with upvalues, Python for nested functions and decorators, and for safe concurrency via captured environments. Their implementation often involves "flat closures" to optimize storage by referencing only free variables, reducing overhead in modern compilers.

Fundamentals

Definition

In , a closure is a function bundled together with references to its surrounding state, specifically its lexical environment, which enables the function to access variables from the outer scope in which it was defined even after that scope has finished executing. This bundling preserves the function's ability to interact with non-local variables, known as free variables, that are not parameters or local to the function itself. The key components of a closure include the function code itself, references to its free variables, and the captured lexical environment that binds those variables at the time of the function's creation. The lexical environment acts as a snapshot or persistent mapping of variable bindings from the defining scope, ensuring that the closure can resolve these bindings consistently regardless of where or when the function is invoked. The primary purpose of closures is to allow functions to maintain and access state from an outer scope without relying on global variables or external structures, thereby supporting modular that encapsulates and privately. This mechanism is particularly valuable in functional and higher-order programming paradigms for creating persistent state without side effects on shared globals. Closures are often created using anonymous functions, which can be defined inline and immediately capture their surrounding environment. For example, consider the following pseudocode that demonstrates a closure forming a function which remembers a value from an outer scope:

function createMultiplier(initialValue) { return function(value) { return initialValue * value; }; } let double = createMultiplier(2); let result = double(5); // Returns 10, as 'initialValue' is captured from the outer scope

function createMultiplier(initialValue) { return function(value) { return initialValue * value; }; } let double = createMultiplier(2); let result = double(5); // Returns 10, as 'initialValue' is captured from the outer scope

In this case, the inner anonymous function forms a closure over initialValue, retaining access to it even after createMultiplier has returned.

Anonymous Functions

, also known as lambda expressions or function literals, are functions defined without binding to a specific identifier, allowing them to be created inline at the point of use for immediate application or passing to other functions. This nameless characteristic distinguishes them from named functions, which are explicitly declared with an identifier for repeated invocation and easier reference throughout a program. Originating from Alonzo Church's in the 1930s, anonymous functions enable the treatment of procedures as first-class values in programming languages supporting functional paradigms. A key feature of anonymous functions is their ability to naturally form closures by capturing variables from their surrounding lexical environment without requiring explicit binding mechanisms. When defined inside another function, an retains references to outer scope variables, preserving access to those values even after the enclosing function has completed execution. This capture occurs at the time of function creation, bundling the function with its relevant environment to enable deferred or nested computations. For instance, in neutral notation, an expression like λx. x + y defines an that takes input x and adds it to y, where y is implicitly captured from the outer scope if not passed as a . In , anonymous functions offer advantages over named functions by facilitating concise, modular code through higher-order operations, such as mapping or filtering collections without polluting the namespace with temporary definitions. They promote compositionality by allowing functions to be generated on-the-fly as arguments or return values, enhancing expressiveness and reducing boilerplate. However, unlike named functions, which support via self-reference and aid in through identifiable calls, anonymous functions are typically non-recursive and best suited for short, single-use logic to avoid complexity in maintenance.

Historical Development

Etymology

The concept of binding free variables in , introduced by in the 1930s, provided foundational ideas for closures in programming by ensuring expressions are self-contained through variable resolution. In programming contexts, the term was first coined by Peter J. Landin in his seminal 1964 paper "The Mechanical Evaluation of Expressions," where he defined a closure as a bundle comprising a lambda expression and the environment relative to which it is evaluated. Landin used this to model the evaluation of applicative expressions via his , emphasizing the pairing of code with its lexical bindings to handle free variables effectively. This introduction marked the term's transition from abstract mathematics to practical language design, resolving issues like the "" in function passing. Landin's work profoundly influenced pioneers in the 1960s, particularly through his abstract language , where closures were formalized as first-class entities supporting lexical scoping and higher-order functions. In , closures encapsulated applicative structures, bridging theory with imperative language features like those in . The term's adoption spread to early implementations, including extensions in , where it evolved to describe the binding of functions to their environments, as later articulated by Joel Moses, who credited Landin for its precise usage in addressing environment capture.

Early Concepts and Implementations

The concept of closures emerged in the late as part of early efforts to implement constructs in practical languages, drawing inspiration from where functions bind to their environments. In 1958, John McCarthy introduced , a language designed for symbolic computation in . Early used dynamic scoping, where variable bindings were resolved based on the call stack at runtime, and while the eval function allowed evaluation in a specified environment and quote preserved expressions, it did not provide automatic lexical closure capture. The in early highlighted challenges with preserving bindings for functions passed as arguments. During the 1960s, the ""—the challenge of passing functions as arguments (particularly upward funargs, where a function returns another function that references variables from the outer scope)—highlighted the need for explicit closure mechanisms to preserve lexical bindings without relying on dynamic scoping. This issue was addressed in pioneering implementations, such as descendants of , including developed by and in 1967, which treated procedures as first-class citizens within class blocks, enabling them to encapsulate local state and influence early object-oriented paradigms by simulating objects through procedure-environment pairs. In the 1970s, Gerald Jay Sussman and Guy L. Steele advanced closure implementations with SCHEME, a of introduced in 1975, which standardized lexical scoping and made closures a core feature for extended interpretation. SCHEME's design emphasized first-class procedures that explicitly carry their defining environments, resolving ambiguities in earlier dynamic scoping models and providing a clean foundation for constructs like continuations and higher-order functions.

Applications

First-Class Functions

In , first-class functions are treated as first-class citizens, meaning they can be assigned to variables, passed as arguments to other functions, returned as results from functions, and stored in data structures alongside other values such as numbers or strings. This capability allows functions to be manipulated dynamically, promoting flexible code organization. Closures facilitate first-class functions by bundling a function with its lexical environment, preserving the bindings of variables from the scope in which the function was defined, even after that scope has ended. This preservation ensures that first-class functions maintain access to their original context when passed or returned, enabling reliable behavior in higher-order functions such as [map](/page/Map) and filter. For instance, [map](/page/Map) applies a provided function to each element of a collection, while filter selects elements based on a predicate function, both relying on the ability to pass functions as arguments. A representative example is a that customizes by accepting a closure as an argument:

function processCollection(collection, processor) { result = emptyList; for each item in collection { result = [append](/page/Append)(result, processor(item)); } return result; } // Usage: double each number in a list doubled = processCollection(numbers, function(x) { return x * 2; });

function processCollection(collection, processor) { result = emptyList; for each item in collection { result = [append](/page/Append)(result, processor(item)); } return result; } // Usage: double each number in a list doubled = processCollection(numbers, function(x) { return x * 2; });

Here, the processor parameter receives a closure that defines the transformation, demonstrating how first-class functions allow behavioral customization without altering the core logic. The use of closures with first-class functions yields key benefits, including enhanced through encapsulated modules and iterators, greater by parameterizing behavior, and support for paradigms that avoid traditional class-based structures. These features enable reusable, composable code that emphasizes immutability and composition over mutable state management.

State Capture and Representation

In , closures capture variables from their enclosing lexical scope by creating a persistent to the environment in which the closure is defined, rather than copying values at that moment. This mechanism, often termed capture by , allows the closure to access and potentially modify the captured variables even after the outer function has returned, as the environment remains live on the heap. In contrast, some implementations opt for capture by value, where the current values of variables are copied into the closure's environment at definition time, preventing later mutations from affecting the closure but potentially increasing usage for large or complex structures. This snapshot-like preservation of the lexical environment at definition time ensures that the closure operates within a consistent context, independent of subsequent changes in the global or outer scopes. Closures are typically represented as a composite consisting of the function paired with a heap-allocated environment record that binds the free variables referenced within the . The environment record serves as a mapping from variable names to their storage locations or values, enabling the closure to resolve free variables during execution by looking up entries in this record. This representation facilitates sharing, as multiple closures can reference the same object while maintaining distinct environments, and supports first-class treatment of functions in higher-order programming. In languages like Scheme, the procedure value explicitly includes this environment as part of its semantic domain, often implemented as a pointer to an activation record extended with captured bindings. A classic illustration of state capture is the counter example, where a factory function generates independent incrementing closures each with private state. In Scheme, this can be expressed as:

(define make-counter (lambda () (let ((count 0)) (lambda () (set! count (+ count 1)) count))))

(define make-counter (lambda () (let ((count 0)) (lambda () (set! count (+ count 1)) count))))

Here, each invocation of make-counter returns a new closure that captures its own count variable by reference, allowing successive calls to the returned function—such as (let ((c1 (make-counter)) (c2 (make-counter))) (list (c1) (c1) (c2))) yielding (1 2 1)—to maintain and mutate isolated state without interfering with other instances. This demonstrates how the closure's environment record preserves the binding of count across invocations, enabling persistent, mutable state within a lexical scope. Compared to global variables, which expose state across the entire program and risk unintended modifications leading to side effects, closures provide superior encapsulation by localizing access to captured variables solely through the closure's interface. This design promotes , as the private state is shielded from external interference, reducing bugs from namespace pollution and enabling safer composition in concurrent or multi-module codebases. Similarly, over instance variables in object-oriented paradigms, closures avoid the overhead of full object allocation while achieving comparable data hiding, particularly in functional contexts where higher-order functions leverage this captured state for operations like iterators or event accumulators.

Additional Practical Uses

Closures find extensive application in handling callbacks and event handlers, particularly in asynchronous programming scenarios. By capturing the surrounding lexical environment, a closure ensures that a callback function retains access to relevant variables even after the initial function has returned, allowing it to preserve context across delayed executions. This is especially valuable in event-driven systems, such as user interfaces, where an event handler created within a specific scope can remember user-specific or configuration without relying on global state. In and techniques, closures enable the creation of specialized functions by pre-filling certain arguments with captured values from the outer scope. This approach allows developers to generate customized variants of a general function, promoting code reusability and modularity without duplicating logic. For instance, a closure can "lock in" configuration parameters, producing a new function that operates on the remaining inputs while maintaining the fixed values internally. Closures also serve as iteration helpers in generators and iterators, where they maintain loop state or progress without introducing external mutable variables. This encapsulation of iteration context ensures that each invocation of the iterator function can access and update the necessary state privately, facilitating clean and efficient traversal mechanisms. A key benefit of closures lies in their role in achieving data , particularly through the implementation of modules or factory functions that shield internal state from external access. By returning an object containing functions that reference but do not expose the captured variables, a closure creates a boundary around private data, enforcing encapsulation and preventing unintended modifications. This pattern is instrumental in simulating private members in languages lacking built-in privacy controls, thereby enhancing and in larger systems.

Theoretical Foundations

Implementation Approaches

Implementations of closures in compilers and interpreters typically represent the captured lexical environment using data structures such as stacks, heaps, or displays to store free variables. In stack-based representations, the environment is maintained as part of the call stack's activation records, allowing efficient access during function execution but requiring careful management to handle closures that outlive their stack frames. Heap allocation is common for persistent environments, where captured variables are stored in dynamically allocated records or linked lists, enabling the closure to retain state after the defining function returns. Display structures, also referred to as flat closures, employ an of pointers to relevant activation records, providing constant-time access to variables in languages with static scoping by avoiding traversal of nested frames. For resolving free variables in closures, two primary binding strategies are employed: deep binding and shallow binding. Deep binding captures the environment at the time of closure creation, explicitly associating the current bindings with the closure object for subsequent lookups, which is straightforward to implement with heap-allocated records but can lead to space duplication if variables are shared across closures. Shallow binding, in contrast, uses a global environment with indirection cells or value cells that are updated dynamically during execution, allowing faster binding operations in some cases but incurring higher lookup costs due to potential traversals. To mitigate performance costs, modern implementations apply optimization techniques such as and inlining. Escape analysis examines whether a closure or its captured variables escape the current stack frame, permitting stack allocation for non-escaping cases to avoid heap overhead; for instance, if a closure is only used locally, its environment can remain on the stack, reducing garbage collection pressure. Inlining substitutes the closure's body directly into the call site, eliminating the need for environment construction and lookup, particularly effective for small, frequently invoked closures. Runtime overhead for closures arises from memory allocation for environments and the costs of variable lookups during invocation. Heap-allocated environments typically require O(1) to O(d) time for lookups, where d is the nesting depth, and incur allocation costs proportional to the number of captured variables. The following pseudocode illustrates a basic deep-binding implementation for closure creation and invocation, using a record for the environment:

pseudocode

// Closure creation (deep binding) function createClosure(lambdaCode, currentEnv): closure = { code: lambdaCode, env: copy(currentEnv) } return closure // Closure invocation function invokeClosure(closure, args): extendedEnv = merge(closure.env, args) return execute(closure.code, extendedEnv)

// Closure creation (deep binding) function createClosure(lambdaCode, currentEnv): closure = { code: lambdaCode, env: copy(currentEnv) } return closure // Closure invocation function invokeClosure(closure, args): extendedEnv = merge(closure.env, args) return execute(closure.code, extendedEnv)

This approach ensures lexical scoping but may introduce overhead from environment copying; optimizations like sharing sub-environments can reduce this by up to 43% in practice.

Connection to

serves as the theoretical foundation for closures in , providing a based on function abstraction, application, and substitution to model . Abstraction, denoted as λx.M\lambda x.M, binds the variable xx in the term MM, representing a function that maps xx to MM. Application, written as (MN)(M N), denotes the invocation of the function MM with argument NN. Substitution, M[x:=N]M[x := N], replaces free occurrences of xx in MM with NN, subject to the no-capture condition to prevent unintended variable binding, and forms the basis of β\beta-reduction: (λx.M)NM[x:=N](\lambda x.M) N \to M[x := N]. In this framework, a closure corresponds to a term with its free variables bound by a surrounding context or environment, ensuring that unbound variables retain their original bindings during . Formally, a closure can be viewed as equivalent to the application (λx.M)N(\lambda x.M) N, where the environment captures the values NN for the free variables in MM, avoiding the need for full substitution at creation time. Free variables in a term MM are those not bound by any within MM, and the environment provides a mapping to resolve them, denoted as a pair (λx.M,ρ)(\lambda x.M, \rho) where ρ\rho binds the free variables. Different reduction strategies in affect how closures are evaluated, particularly in handling arguments and environments. Call-by-value (CBV) requires arguments to be reduced to values (typically lambda abstractions) before β\beta-reduction, ensuring that the environment bindings in a closure are fully evaluated prior to application, which can prevent certain non-terminations but may compute unnecessary values. In contrast, call-by-name (CBN) reduces the outermost redex first, deferring argument until substitution into the closure's body, aligning with and preserving the unevaluated form in the environment to avoid redundant computations. These strategies influence closure evaluation by determining when environment lookups occur, with CBV promoting eager binding resolution and CBN enabling delayed substitution. Closures address the —arising when functions with free variables are passed or returned as arguments—through explicit environment capture, eschewing textual substitution to prevent binding errors. In terms, the problem manifests during β\beta-reduction of open terms, where substituting into a with free variables risks variable capture or loss of lexical scope. A closure solves this by pairing the with a static environment (e.g., a chain), so evaluation reconstructs the bindings without altering the term: for a closure (λz.M::S)(\lambda z.M :: S), where SS binds free variables in MM, application to an argument yields M[z:=arg,free vars from S]M[z := \text{arg}, \text{free vars from } S] via environment extension rather than direct substitution. This approach maintains and avoids the computational overhead of repeated substitutions, as the environment acts as a virtual substitution mechanism.

Semantic Variations

Lexical Environment and Scoping

In lexical scoping, also known as static scoping, the scope and binding of a variable are determined by the textual structure of the program at the time of , enabling closures to reliably capture and access variables from their enclosing environment. This static binding contrasts with dynamic scoping, where variable resolution depends on the runtime and the context of the caller, which generally prevents the formation of persistent closures that retain access to the original environment. Lexical scoping thus supports closures by fixing variable references to the lexical context, ensuring predictable behavior independent of execution path. Variable resolution in a lexical environment proceeds through a chain of scopes, starting from the innermost environment and traversing outward to enclosing ones until is found or the global scope is reached. Closures preserve this scope chain as part of their structure, maintaining references to the environment active at the time of their creation, which allows them to access non-local variables long after the defining scope has terminated. This chain-based lookup mechanism, often implemented via environment records or activation frames, ensures that free variables in a closure are resolved consistently against the captured lexical contour. Nested scopes extend this model by allowing inner functions to form closures that capture variables from multiple outer levels, with resolution following the hierarchical chain. For instance, in a textual representation using pseudocode, the scope chain can be depicted as a linked structure of environments:

Global Environment = { var: 'global_value', outer: null } Outer Environment = { outer_var: 'outer_value', outer: Global Environment } Inner Environment = { inner_var: 'inner_value', outer: Outer Environment }

Global Environment = { var: 'global_value', outer: null } Outer Environment = { outer_var: 'outer_value', outer: Global Environment } Inner Environment = { inner_var: 'inner_value', outer: Outer Environment }

Here, a closure defined in the inner environment resolves inner_var locally, falls back to outer_var in the outer environment, and ultimately to global_value in the global environment if needed, preserving the full chain for subsequent invocations. This nested capture facilitates modular code organization while upholding lexical binding rules.

Handling Unbound Variables

In closures, unbound free variables refer to identifiers referenced within the function body that lack corresponding bindings in the captured lexical environment at the time of closure creation or invocation. When such a closure is executed, most programming languages raise a runtime if the variable remains unresolved, as the closure cannot access a value for it. For instance, in Python, attempting to access an unbound free variable triggers a NameError, emphasizing that free variables must be resolved from the enclosing scope's bindings. Similarly, in , an unresolved free variable in a closure leads to a ReferenceError during , as the scope chain fails to locate the identifier. In Scheme and , referencing an unbound variable in a closure signals an "unbound variable" , preventing execution until the is resolved. A common scenario arises when a closure references a variable defined after its creation in the enclosing scope, leading to a runtime failure if the assignment is not executed. Consider this Python example:

python

def outer(): def inner(): return x # References x, which is unbound at this point return inner x = 10 # Assigned after inner is defined but unreachable due to return f = outer() f() # Raises NameError: free variable 'x' referenced before assignment in enclosing scope

def outer(): def inner(): return x # References x, which is unbound at this point return inner x = 10 # Assigned after inner is defined but unreachable due to return f = outer() f() # Raises NameError: free variable 'x' referenced before assignment in enclosing scope

Here, the closure captures a cell for x, but since the assignment occurs after the return statement and is thus not executed, the cell remains unbound at invocation time, resulting in the error. This behavior underscores the dynamic resolution of free variables in Python's implementation of lexical scoping. To mitigate issues with unbound variables, several strategies are employed across languages. Explicit passing of values as function parameters allows closures to receive necessary data without relying on the captured environment, promoting modularity and reducing scope dependencies. Default values can be provided in some implementations; for example, in JavaScript, unresolved variables may fall back to undefined if they reach the global scope without erroring earlier, though strict mode enforces stricter checking. Language-specific globals offer another approach, where unbound variables are resolved from a global namespace, as seen in Common Lisp's dynamic variables or JavaScript's global object, though this can introduce non-lexical behavior. These handling mechanisms carry significant implications for code reliability. Runtime errors from unbound variables often manifest only upon invocation, complicating in large codebases where closures may be returned or passed asynchronously. Moreover, inadequate management of free variables can compromise in functional code, leading to subtle bugs from unintended scope interactions or variable mutations, as closures sharing bindings may unexpectedly alter shared state. Under lexical scoping, which identifies unbound variables through static scope analysis, developers must ensure all free variables are properly bound at creation to maintain predictable behavior.

Interactions with Bound Variables

In languages supporting lexical scoping, such as Scheme and , closures capture references to variables from their defining lexical environment, enabling access to outer-scope bindings even after the outer function returns. Variable shadowing arises when an inner scope declares a new binding for a variable sharing the name of an outer-scope variable, thereby hiding the outer binding within the inner scope. A closure defined within this inner scope that references the shared name will capture the inner (shadowed) binding, which may lead to the unintended reference rather than the expected outer one, as the resolution occurs at the point of closure creation based on the active environment. This interaction can cause unexpected behavior if the programmer assumes the closure will access the outer variable. For instance, consider the following example, adapted from Go's scoping rules where a closure introduces a shadowed variable:

func outer() int { x := 10 return func() int { x := 20 // Shadows the outer x return x }() }

func outer() int { x := 10 return func() int { x := 20 // Shadows the outer x return x }() }

Here, the anonymous inner function (a closure) declares its own x, shadowing the outer x from the enclosing scope. When invoked, it returns 20 instead of the anticipated 10, as it captures the local shadowed binding rather than the outer reference. To resolve such capture errors and ensure the closure references the intended outer variable, developers can employ let-bindings to introduce a distinct name in a new scope or use explicit qualifiers (such as qualified names in module systems) to disambiguate references. In Scheme, for example, a creates a fresh environment frame, allowing the outer binding to be aliased or preserved without conflict:

(define outer-x 10) (let ((local-x outer-x)) ; Binds local-x to the value of outer-x (lambda () local-x)) ; Closure captures local-x, avoiding direct shadowing

(define outer-x 10) (let ((local-x outer-x)) ; Binds local-x to the value of outer-x (lambda () local-x)) ; Closure captures local-x, avoiding direct shadowing

This approach isolates the capture, preventing accidental hiding of the outer binding. Best practices for managing these interactions include avoiding mutable outer variables within closures, as mutations to shared captured state can introduce side effects and race conditions in concurrent contexts, undermining the predictability of functional-style programming. Instead, prefer immutable captures or explicit state passing to maintain referential transparency and reduce debugging complexity.

Language Constructs

Closures in Functional Languages

In languages, closures are a fundamental mechanism for implementing lexical scoping with first-class functions, allowing procedures to capture and retain access to their defining environment. Scheme, a dialect of , supports lexical closures through expressions, which create procedures that remember the environment in effect at the time of . This enables the procedure to access variables from its lexical context even after the defining scope has exited. For instance, the expression (define add4 (let ((x 4)) ([lambda](/page/Lambda) (y) (+ x y)))) produces a closure that captures the value of x, returning 8 when applied to 4. Scheme employs strict lexical scoping, where variable bindings are determined by the program's textual structure, ensuring predictable behavior in higher-order functions. Haskell, a purely functional language, integrates closures seamlessly into its lazy evaluation model, where lambda abstractions and let bindings capture values from the surrounding environment without side effects due to immutability. Every function in Haskell is effectively a closure, as it may reference free variables from its lexical scope, with the captured environment preserved through thunks for deferred computation. This supports pure closures even in monadic contexts, such as IO, where side-effecting actions are sequenced without mutating captured state. An example is let x = 4 in \y -> x + y, which forms a closure over x and evaluates lazily only when the result is demanded. Haskell's emphasis on immutability ensures that closures maintain , aligning with its roots in . Standard ML (SML) realizes closures through anonymous functions defined via fn expressions, which form function closures consisting of a pattern match, the lexical environment, and recursive bindings, enabling polymorphism in . Lexical scoping in SML binds variables to their defining context, with closures capturing this environment to support modular code via —parametric modules that instantiate structures while preserving . For example, a might define a that applies a polymorphic closure over a structure's types, as in functor F (S : SIG) = struct val add = fn x => fn y => S.int + x + y end. SML integrates closures with , allowing concise handling of data while enforcing strict typing and immutability in functional compositions. Common across these languages are strict lexical scoping, an emphasis on immutability to avoid shared mutable state, and deep integration with for expressive function definitions.

Closure-Like Features in Imperative Languages

In imperative languages, which prioritize explicit control flow and mutable state over functional paradigms, closure-like features are typically implemented through workarounds that approximate the capture of lexical environments. These adaptations allow functions to access variables from their defining scope but often require manual intervention for state management and lack the seamless integration seen in functional languages. Common approaches involve combining function pointers or object-oriented constructs with mechanisms to preserve captured data, though they introduce complexities in memory handling and scoping. In the C language, a low-level imperative system without built-in higher-order functions, closures are simulated using paired with or structs to encapsulate state. For instance, a struct can hold a alongside data members representing captured variables, enabling the function to access that state upon . This technique allows basic lexical retention, such as a counter that persists across calls via a within the function. However, it imposes limitations on dynamic capture, as local variables cannot be automatically enclosed without explicit struct initialization and passing, and there is no support for nested or recursive environments without additional boilerplate. Java, an object-oriented imperative language, emulates closures prior to Java 8 through local classes and anonymous inner classes, which capture variables from the enclosing scope. Anonymous inner classes, declared and instantiated inline, access local variables provided they are declared final or effectively final, effectively closing over the lexical to form a callable object implementing an interface. Local classes, defined within a method, similarly capture enclosing variables but offer named reuse within the block. These constructs enable functional-style callbacks, such as event handlers, but require verbose syntax and restrict modifications to captured variables to prevent concurrency issues. In C#, delegates serve as the foundation for closure-like behavior, with anonymous methods (introduced in C# 2.0) allowing inline definition of callable code that captures variables from the surrounding scope. An anonymous method assigned to a delegate captures local variables by reference, meaning changes to those variables after creation affect the delegate's execution, as the generates a closure class to hold references to the captured environment. For example, a delegate filtering a list by a variable threshold can dynamically adjust based on external updates to that variable. This by-reference capture supports mutable state but can lead to unexpected side effects in loops or asynchronous scenarios if not handled carefully. Implementing these features in imperative languages presents challenges, particularly manual memory management in systems like , where captured state in structs must be explicitly allocated and freed to avoid leaks or dangling pointers, as there is no automatic garbage collection for enclosed environments. Additionally, the absence of native lexical binding often results in error-prone code, with risks of scope violations or performance overhead from manual state passing. Similarities exist in , where blocks approximate closures by capturing enclosing variables, though they integrate more directly with the runtime.

Variations in Modern Languages

In , the introduction of 6 (ES6) in 2015 brought significant enhancements to closure handling through arrow functions, which lexically bind the this keyword to the enclosing scope rather than dynamically binding it, reducing common pitfalls in callback-based closures. Additionally, block scoping with let and const declarations, also part of ES6, ensures variables captured by closures are confined to their block, mitigating hoisting issues that plagued var declarations in earlier versions and enabling safer captures in nested functions. Async arrow functions, available since ES2017, further extend this by allowing asynchronous closures that capture lexical environments while returning promises, facilitating cleaner handling of concurrent operations like API calls without altering the closure's binding behavior. Python supports closures natively through nested functions and lambda expressions, where inner functions capture variables from the outer scope by , but modifications to those captured variables require the nonlocal keyword, introduced in Python 3.0 (2008) to explicitly allow rebinding in enclosing namespaces. This enables mutable captures, as seen in examples where a nested function updates a counter from its outer scope: def outer(): count = 0; def inner(): nonlocal count; count += 1; return inner. Lambda functions, being anonymous, also form closures but are limited to expressions, often used for concise arguments. Decorators, a Python idiom since version 2.4 (2004) but refined in later releases, heavily rely on closures to wrap functions and add behavior like or caching, where the decorator factory returns a closure that captures the original function and arguments. Rust treats closures as first-class citizens implemented via traits—Fn for immutable borrows, FnMut for mutable borrows, and FnOnce for consuming moves—allowing precise control over how captured variables are accessed without runtime overhead. The ownership system and borrow checker enforce these captures at , preventing dangling references by ensuring align; for instance, a closure capturing a borrowed value cannot outlive the lender, which is verified statically to avoid memory errors common in languages without such guarantees. This design extends to concurrency, where closures passed to threads must implement Send and Sync traits to safely share state across threads, leveraging the borrow checker to eliminate data races. Since the 2010s, closures in these languages have seen optimizations for performance and concurrency: JavaScript's improvements post-ES6 enable of arrow closures for faster execution in event-driven environments, and Rust's zero-cost abstractions ensure closures incur no runtime penalty while its async/await syntax (stable since ) integrates closures seamlessly for concurrent tasks without blocking threads. These evolutions address in multi-core and distributed systems, with Rust's borrow checker providing compile-time safety that outperforms garbage-collected alternatives in high-concurrency scenarios.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.