Closure function

from Wikipedia, the free encyclopedia

A closure (or function closure ) is a concept from functional programming . It describes an anonymous function that contains access to its creation context. When called, the function then accesses this creation context. This context (memory area, status) cannot be referenced outside of the function, i. H. not visible.

A closure contains a reference to the function and the part of the creation context used by it - the function and the associated memory structure are inseparably closed in a reference (closed term). It is like an object with attributes and methods: it contains an implicit identity, a state and a behavior.

In programming language syntax, this is often achieved by two nested functions - the inner main function is enclosed (closed) by another function. This termination function contains the required memory structure (see examples below). It is constructed in such a way that when it is called it provides a reference to the inner function together with the required variables (the memory structure). Strictly speaking, the closure is not the inner function alone, but the bundling of (inner) function and variable state that the reference points to.

A closure can also be viewed as an object that usually only has one method. If the closure is created together with other closures using the same context, it is an object with several methods. The variables from the generating area included in the closure can be used as attributes by the closure.

origin

Closures are a concept that stems from the functional programming languages , appeared for the first time in Lisp and was fully supported for the first time in its dialect Scheme . As a result, it was also supported in most of the later functional programming languages ​​( e.g. Haskell , Ocaml ).

Advantages and features

With closures, areas that are not visible but can be changed in a controlled manner can be created, for example data encapsulation or currying can be implemented.

Creating a closure is much less work than creating a class with just one method. Following an object-oriented view, closures are suitable for the quick creation of an object-like structure without a class. Often an anonymous function is used as the inner method.

In a purely functional programming language, a closure can always be used when it is either to be called as a function itself or to be included as a parameter in a function call. In the latter case, it can act as a call-back function generated at runtime and thus enables an application program to manipulate its own control flow to a considerable extent during its runtime. However, this is usually only made possible in a practically meaningful way by a system of closures. The didactic problem of teaching inexperienced programmers the use of closures is based on this fact.

Example in pseudocode

In the following example, a function is first mutterfunktiondefined. This function sets a local variable named kuchentypand defines a local function named kindfunktion.

funktion mutterfunktion {
    setze kuchentyp = 'Apfelkuchen'
    funktion kindfunktion {
        gib_aus 'Ich esse #{kuchentyp}'
    }
    gib_zurück kindfunktion
}

When called, mutterfunktionthe local function kindfunktionreturns (not its result!). (This is technically also known as a function pointer in non-functional programming languages ​​such as C and relatives. A typed function pointer is called a delegate .)

setze meinkuchen = rufe_auf mutterfunktion

The meinkuchenfunction is kindfunktionassigned to the global variable .

rufe_auf meinkuchen

The subsequent call to meinkuchenwill therefore kindfunktionexecute. Although no global variable kuchentypexists, are kindfunktionthe string 'Ich esse Apfelkuchen' , because they can access their creation context in which the variable kuchentypto 'Apfelkuchen'be defined. The decisive factor is that although mutterfunktiona value has already been returned - the context actually no longer exists - it can kindfunktionaccess it - it kindfunktionis therefore a closure function.

[Ausgabe:] Ich esse Apfelkuchen

With a change in the code of the value of the variable will be anzahl_kuchenthe mutterfunktionincreased with each access to the Closure function by one, which can be realized a counter. The value in anzahl_kuchenis protected from manipulation and can only be essenincreased by.

funktion mutterfunktion {
    setze anzahl_kuchen = 0
    funktion kindfunktion {
        setze  anzahl_kuchen = anzahl_kuchen + 1
        gib_aus 'Ich esse #{anzahl_kuchen} Kuchen'
    }
    gib_zurück kindfunktion
}

With multiple calls to the parent function from other program parts, the actually no longer visible value of the local variable <number_cake> can only be accessed indirectly, and (only) within the kindfunktion(encapsulated) calculations can be made with otherwise unchangeable values ​​- this shows the Main advantages of closures mentioned:

setze essen = rufe_auf mutterfunktion
rufe_auf essen
rufe_auf essen
rufe_auf essen
Ich esse 1 Kuchen
Ich esse 2 Kuchen
Ich esse 3 Kuchen

Direct access to the variable anzahl_kuchenis protected in this way; its value can (as in the example) or could not be passed directly to the outside world. Under no circumstances can the value be changed externally, so closures offer more access protection than fields of a class declared as "private", for example in Java or C # , which can be easily circumvented with reflection , for example .

How you interpret this depends heavily on your own perspective on programming languages. From an object-oriented point of view, the parent function takes on the role of a class, more precisely an object (the instance of a class) and, from an object-oriented point of view, encapsulates child variables with child function (s) to form a unit.

Viewed differently, a kind of cross-call “memory” is implemented in the functions, similar to a static variable, only more powerful. Viewed a little differently, this can also be seen as a change in the control flow, as the above example shows very well. Lists can be implemented as a function call, for example, since a different result can be delivered with each call (due to the "memory"). C # uses this as a special case when implementing "yield return". For each call, the next element of an enumerable type such as a list, so to speak "lazy" , ie. H. To save resources only to be returned when needed.

Conceptual requirements for closures in programming languages

As mentioned, closures represent a pattern of functional programming; they are often difficult to understand for programmers of not purely functional programming languages, even if they can be implemented in an increasing number of programming languages.

The following conceptual "building blocks" are necessary to make a closure feasible in a programming language.

1. Functions must be allowed as return objects of another function, at least via used elements such as function pointers , delegates or lambda expressions. This is also referred to as first-class functions . (The opposite is especially true when functions can only be viewed and used as a kind of named command).

2. In the above example, the inner function must be able to access the variables of the outer function (call environment). In contrast to local variables, these variables are also referred to as “free variables” from the point of view of the internal function.

3. The compiler must be able to recognize that the value (state) of the variable is required outside of its actual scope, and actively take this into account during compilation. Technically, these variables are then usually no longer stored on the stack, but this is solved differently, e.g. B. by actually creating an (anonymous) class including an instance in the background that contains the required (member) variables and the inner function (as a member function).

Only now have all the building blocks come together to create a shortened but more technical definition of the term closure , more precisely of lexical closures in the narrower sense:

Closures are therefore a programming technique or structures for implementing lexical scope with free variables in languages ​​with first-class functions .

Dynamic and Lexical Closures

The first implementation of closures arose from the way Lisp implemented execution environments. In the first Lisp implementations there was no lexical copying . The execution environment of an instruction consisted of a so-called A-list with variable bindings, which could be reached via a single reference. A closure over a function then consisted of a pair, consisting of the function definition and the reference to the A-list valid at the time the closure was defined. This pair created by the Lisp function FUNCTION is a dynamic closure with the historical name FUNARG (FUNctional ARGument). If the FUNARG was executed later, this was done in the context of the A-list brought along instead of in the context of the currently valid A-list.

The lexical sketching used today in Lisp as in all other languages ​​leads to the lexical closure, which is also functional in compiled languages. It only arises through the active intervention of the compiler, in that it identifies the function's references to the variables that are free within and outside of it, and that generates code that combines these relationships with the function into a closure when it is returned from its definition context. This happens before this function - now as closure - is made available to the caller. Since this variable binding is no longer lexically bound, it cannot remain on the stack, but is placed on the heap by the runtime system. If several closures are formed at the same time using the same variable binding, the runtime system ensures that the same heap-based copy of this variable binding is used in both closures.

Implementations

There are also non-functional programming languages ​​that support this function. These include Ada , C ++ (from C ++ 11), C # , Go , Groovy , Java , JavaScript , Lua , Object Pascal (Delphi), PHP , Perl , Python , Ruby , Smalltalk , Swift and Visual Basic .NET . Apple has extended the gcc and clang to include closures - called block literals - for C and proposed this for standardization.

Examples of implementations

Common Lisp

This example uses a closure to enable an elegant database query. The closure is provided by the function name-is. The special function lambda creates a nameless function within which the value of the name field is nchecked for equality with a character string . The call (name-is "Elke")thus provides a closure as a connection between the anonymous function and the variable link from nto the character string "Elke". This can check a data record for the same name as "Elke". The closure can be passed directly to the function filter, which then applies it and returns the result.

(defparameter *dbase*
    '(("Elke"  "1.1.1980") ("Gabi"  "2.3.1981") ("Heidi" "4.5.1982")
      ("Gabi"  "5.6.1983") ("Uschi" "7.8.1984")))

(defun get-name (list)
    (first list))

(defun name-is (name)
    (lambda (list)
        (equal (get-name list) name)))

(defun filter (predicate list)
    (remove-if-not predicate list))

These definitions now make the following elegant query possible:

(print (filter (name-is "Gabi") *dbase*))

It is to be understood as follows: The function (name-is "Gabi")call provides a closure. Here it is a combination of the comparison code (equal (get-name list) name)from the function name-isand the binding of the character string "Gabi"to the variable name. So it is semantically the query (equal (get-name list) "Gabi"). This comparison is passed as closure to the function filterthat uses this comparison. Performing this filtering then leads to the result:

(("Gabi" "2.3.1981") ("Gabi" "5.6.1983"))

Pearl

The context of any code fragment is determined, among other things, by the symbols available:

# pragma
use strict;

sub function {
    # Argumente in benannte Variablen kopieren
    my ($var1, $var2) = @_;

    # block code
}

In the example shown above, the variables $var1and are $var2valid and visible at every point in the function. When you exit the function, they are cleared up together with the abandoned block ("go" out of scope ) and are then unknown. Any further access would be a mistake.

Closures now offer the possibility of extending the scope of such variables beyond their official end. To do this, a function is simply defined in the scope that uses the relevant variables:

# pragma
use strict;

sub function {
    my ($var1, $var2) = @_;
    return sub { print "Vars: $var1, $var2.\n" };
}

my $f = function("Hallo", 8);
my $g = function("bar", "Y");

# Aufruf von $f
$f->();

# Aufruf von $g
$g->();

When the function is functionexited, the runtime system now detects that references to the block variables $var1and still $var2exist - the return value is an anonymous subroutine , which in turn contains references to the block variables. $var1and $var2are therefore retained with their current values. Because the function conserves the variables in this way, it becomes a closure.

In other words, even after leaving the actual validity range of the variable, you can execute the call $f->()and the call at any time and the $g->()result will always be shown the values ​​of the variables that were valid when the functions were defined.

This gives the output:

Vars: Hallo, 8.
Vars: bar, Y.

You can no longer change these values ​​because the variables are no longer available outside the closure. But this is mainly due to the function definition: Of course, the closure could not only have output the values, but also edit them or make them available to the calling code by reference. In the following variant, for example, functions for incrementing and decrementing are introduced:

# pragma
use strict;

# function
sub function {
    my ($var1, $var2) = @_;

    return (
        sub {print "Vars: $var1, $var2.\n"},
        sub {$var1++; $var2++;},
        sub {$var1--; $var2--;}
    );
}

# call the function
my ($printer, $incrementor, $decrementor) = function(3,5);
# use closures
$printer->();
$incrementor->();
$printer->();
$incrementor->();
$incrementor->();
$printer->();

This gives the output:

Vars: 3, 5.
Vars: 4, 6.
Vars: 6, 8.

Closures can be used, for example, to encapsulate access to sensitive data.

python

The following is a simple example of a counter in Python that works without a (named) container that stores the current counter reading.

def closure():
    container = [0]

    def inc():
        container[0] += 1

    def get():
        return container[0]

    return inc, get

In the example, closuretwo function objects are created within the function, both of which containerreference the list from their respective higher-level scope. If the closurefunction has been processed (after a call) and the two returned function objects continue to be referenced, the containerlist continues to exist , although the closure scope has already been exited. In this way, the list is preserved in an anonymous scope. The list cannot be containeraccessed directly. If the two function objects incand are getno longer referenced, the container also disappears.

The closure in the previous example is then used in the following way:

>>> i, g = closure()
>>> g()
0
>>> i()
>>> i()
>>> g()
2

OCaml

OCaml allows this in the following way:

let counter, inc, reset =
    let n = ref 0 in
        (function () -> !n),    	 (* counter  *)
        (function () -> n:= !n + 1), (* incrementor *)
        (function () -> n:=0 )     	 (* reset  *)

now the counter can be used as follows:

# counter();; (* ergibt 0 *)
# inc();;
# counter();; (* ergibt 1 *)
# inc();inc();inc();;
# counter();; (* ergibt 4 *)
# reset();;
# counter();; (* ergibt 0 *)
# n;; (* n ist gekapselt *)
Unbound value n

Instead of an integer, any objects or variables of any type can of course be encapsulated in this way.

JavaScript

In the function f1, another function f2 is defined as a closure;

let f1 = function() {      // eine äußere Funktion f1 definieren ...
    let wert = 22;          // ... und darin einen Namensraum erstellen.

    let f2 = function() {  // eine innere Funktion definieren, ...
        return wert;          // ... die den Namensraum nach außen reicht.
    }

    return f2;   // f2 durch f1 zurückgeben, womit f2 zum closure wird.
}

let a = f1(); // a ist die von f1() zurückgegebene closure-Funktion, ...
console.log(f1()); // ... also: function() {return wert;}
console.log(typeof wert); // ist undefined
console.log(a()); // ergibt 22
console.log(f1()()); // ergibt 22, f2() ist hier aber nicht abrufbar

The above example formulated a little differently, the inner function is now called directly:

let f3 = function() {
    let wert = 23;

    // die Funktion f3 gibt gleich die closure-Funktion zurück!
    return function() {
        return wert;
    };
}

let b = f3(); // b ist wieder die von f3() zurückgegebene Funktion ...
console.log(b()); // ... und liefert jetzt als Ergebnis 23
console.log(b); // b bleibt aber weiterhin ein Funktionsaufruf!
console.log(f3()()); // liefert ebenfalls 23

The embedded function serves as the supplier of the value defined in the higher-level function.

The superordinate function can also be defined as an anonymous function :

let wert = 24;

let c = (function() { // die äußere als anonyme Funktion und ...
    return wert;        // ... darin die innere Funktion definieren.
}());                   // die Funktion jetzt noch mit (); aufrufen.

console.log(c);         // ergibt 24

The closure can also be created with a constructor function:

let d = (new Function("return wert;"))();   // mit einem Konstruktor definieren und aufrufen

Lua

Lua has built-in and, in terms of programming, also intuitively usable support for closures, the implementation of which is similar to that in Python:

function adder(x)      -- Funktionserzeuger
    return function(y) -- anonyme, zu adder private Funktion
        return x+y     -- x stammt hier aus dem äußeren Kontext
    end
end

An example usage would look like this:

add2 = adder(2)    -- hier wird die Closure erzeugt
print(add2(10))    --> Ausgabe 12
print(add2(-2))    --> Ausgabe 0

A closure implementation in Lua is described in.

Erlang

Erlang as a functional language also has closures, which are, however, called funs (singular fun , from function ).

do_something(Fun) -> Fun(4).

main() ->
    Var = 37,
    F = fun(N) -> Var + N end.
    Result = do_something(F).
    % Result =:= 41 =:= 37 + 4

C #

C # supports closures in the form of delegates .

private static Action CreateClosure()
{
    // Deklaration einer Variablen im lokalen Kontext
    var x = 0;

    // Erstellung eines Closure-Delegate mit Hilfe eines Lambda-Ausdrucks
    Action closure = () => Console.WriteLine(x);

    // Änderung am lokalen Kontext
    x = 1;

    // Rückgabe der Closure in den übergeordneten Kontext
    return closure;
}

static void Main()
{
    var closure = CreateClosure();

    // Im globalen Kontext
    // Variable x wird nur noch innerhalb der Closure referenziert

    // Führe Closure aus; Schreibt "1" auf die Konsole
    closure();
}

C ++ 14

C ++ supports Closures means lambda expressions (from C ++ 11) extending in function objects can be called radio doors, of the type std :: function capsules.

#include <string>
#include <iostream>

auto create_closure() {
    std::string kuchen("Apfelkuchen");

    // Lokale Variablen werden hier als Kopie in das Funktionsobjekt übertragen
    return [=]() { std::cout << "Ich esse " << kuchen << std::endl; };
}

int main() {
    auto closure = create_closure();
    closure();

    return 0;
}

With the help of the keyword mutable , a real closure can be created from a lambda function, which not only has its own variables, but can also change them (the variable "anzahl_kuchen" in the outer block is not changed, but only a copy of it ):

#include <iostream>

auto mutterfunktion() {
    int anzahl_kuchen = 0;

    // Die übernommene Kopie der Variable kann hier zusätzlich ihren Wert verändern.
    return [=]() mutable { std::cout << "Ich esse " << ++anzahl_kuchen << " Kuchen.\n"; };
}

int main() {
    auto essen = mutterfunktion();

    essen();
    essen();
    essen();

    return 0;
}

Edition of this program:

Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 3 Kuchen.

Java

In Java , as of version 8, closures are also possible, whereby some specific language assumptions about lambda expressions must be observed. For example, the following code would not compile .

private static Function<String, Supplier<String>> motherFunc = cakeName -> {
    int i = 0;
    return () -> "Ich esse " + i++ + " " + cakeName; // Fehler: i sollte final oder effectively final sein
};

public static void main(String[] args) {
    Supplier<String> käsekuchen = motherFunc.apply("Käsekuchen");

    System.out.println(käsekuchen.get());
    System.out.println(käsekuchen.get());
    System.out.println(käsekuchen.get());
}

Java assumes that all lambda expressions are pure; H. the same input values ​​give the same output values, since no side effects occur. This is done by javac to compile-time checks and HotSpot used at runtime to optimize. In the above example this is not the case, because the returned supplier i++has side effects, which triggers a compiler error. Side effects are a source of errors, especially in a multithreaded environment, and according to the specifications, HotSpot may assume that a method or a lambda is only called by one thread, as long as neither the method, nor an object or a submethod the keyword is synchronizedused in the method . The deception of the compiler by int i[] = {0}and i[0]++outwitting javac, but does not prevent HotSpot from finding optimizations that fail when called by multiple threads. The functionally correct solution is therefore to implement the incrementation through the package java.util.concurrent.atomic, for example with AtomicInteger:

private static Function<String, Supplier<String>> motherFunc = cakeName -> {
    AtomicInteger atomicInteger = new AtomicInteger();
    return () -> "Ich esse " + atomicInteger.getAndIncrement() + " " + cakeName;
};

public static void main(String[] args) {
    Supplier<String> käsekuchen = motherFunc.apply("Käsekuchen");

    System.out.println(käsekuchen.get());
    System.out.println(käsekuchen.get());
    System.out.println(käsekuchen.get());
}

The methods of the Atomic classes are not synchronized, but the instructions run atomically at the processor level, which has the same effect. The code compiles because the reference to the counter object in the lambda expression remains unchanged, and it is effectively final . Possible side effects with multithreading are solved, even if HotSpot performs optimizations. The output is then:

Ich esse 0 Käsekuchen
Ich esse 1 Käsekuchen
Ich esse 2 Käsekuchen

PHP

PHP supports closures from version 5.3.0 in the form of anonymous functions.

Technically, PHP solves the implementation of this functionality with its own "Closure" class.

$mutterfunktion = function() {
    $anzahl_kuchen = 0;

    $kindfunktion = function() use (&$anzahl_kuchen) {
        $anzahl_kuchen = $anzahl_kuchen + 1;
        print "Ich esse {$anzahl_kuchen} Kuchen\n";
    };

    return $kindfunktion;
};

$essen = $mutterfunktion();
$essen();
$essen();
$essen();

The output of the calls is as follows:

Ich esse 1 Kuchen
Ich esse 2 Kuchen
Ich esse 3 Kuchen

As of PHP 7.0, closures will also be supported in the form of anonymous classes.

$essen = new class() {
    private $anzahl_kuchen = 0;

    public function __invoke() {
        $this->anzahl_kuchen = $this->anzahl_kuchen + 1;
        print "Ich esse {$this->anzahl_kuchen} Kuchen\n";
    }
};

$essen();
$essen();
$essen();

Both implementations provide identical outputs.

Rust

Rust supported closures from version 0.1 onwards, up to Rust 1.26 (published on May 10, 2018), closures had to be returned from functions via a pointer to the heap memory Box.

fn mutterfunktion() -> Box<FnMut() -> ()> {
    let mut anzahl_kuchen = 0;

    let kindfunktion = move || {
        anzahl_kuchen += 1;
        println!("Ich esse {} Kuchen", anzahl_kuchen);
    };

    // [Ex.1] Fehler wenn anzahl_kuchen nicht Copy implementieren würde (s.u.)
    // println!("Jetzt ist die Anzahl der Kuchen: {}", anzahl_kuchen);

    return Box::new(kindfunktion);
}

fn main() {
    let mut essen = mutterfunktion();
    essen();
    essen();
    essen();
}

Output:

Ich esse 1 Kuchen
Ich esse 2 Kuchen
Ich esse 3 Kuchen

In Rust 1.26 the impl Traitsyntax was stabilized, which Box::new()enables the same code without indirection via heap memory ( ):

fn mutterfunktion() -> impl FnMut() -> () {
    let mut anzahl_kuchen = 0;

    move || {
        anzahl_kuchen += 1;
        println!("Ich esse {} Kuchen", anzahl_kuchen);
    }
}

fn main() {
    let mut essen = mutterfunktion();
    essen();
    essen();
    essen();
}

mutterfunktion()The Fntrait "implements" the return value , whereby the exact type of return value is only determined when the function is used.

This differentiated Rust between function pointers and closures, as well as various closure types: Fn, FnMutand FnOnce. A Fnclosure cannot modify the context in which it is called. A FnMut-Closure can only modify the variable in the context if it has been mutmarked as . A FnOnce-Closure consumes the variable created in the context. This could essen()only be called exactly once - at the end of the first closure the destructor runs anzahl_kuchenand the variable is no longer available.

If [Ex.1]commented out, the output is:

Jetzt ist die Anzahl der Kuchen: 0
Ich esse 1 Kuchen
Ich esse 2 Kuchen
Ich esse 3 Kuchen

The movekeyword is used to anzahl_kuchenindicate ownership of the variable . Since our variable can be anzahl_kuchencopied (variables of the type u32implement the Copy-Trait), we can still use the variable within the parent function after the actual value has been passed to the closure. This is anzahl_kuchencopied, i.e. H. although we have already set the number to 1 in the code, the output still outputs 0 because it is a complete copy of the variable. If the type of anzahl_kuchencannot be copied, the compiler issues an error.

literature

  • Ralf H. Güting, Martin Erwig, translator construction . Springer, 1999, ISBN 3-540-65389-9
  • Damian Conway , Object Oriented Perl
  • Oliver Lau, Andreas Linke, Torsten T. Will: Variables to go - Closures in current programming languages. In: c't , 17/2013, p. 168ff.

Web links

Individual evidence

  1. Closure-based State: C #
  2. John McCarthy et al. a .: Lisp 1.5 Programmers Manual . (PDF) softwarepreservation.org; accessed on March 12, 2014.
  3. ^ John Barnes: Rationale for Ada 2005
  4. Closures in Java
  5. Closures in JavaScript (English)
  6. Craig Stuntz: Understanding Anonymous Methods ( Memento of the original from June 6, 2009 in the Internet Archive ) Info: The archive link was inserted automatically and has not yet been checked. Please check the original and archive link according to the instructions and then remove this notice. @1@ 2Template: Webachiv / IABot / blogs.teamb.com
  7. Barry Kelly: Tiburon: fun with generics and anonymous methods .
  8. N1370: Apple's Extensions to C (PDF; 69 kB)
  9. ^ The implementation of Lua 5.0
  10. Dustin Campbell: What's In A Closure. (No longer available online.) February 9, 2007, archived from the original on August 15, 2014 ; accessed on April 12, 2014 . Info: The archive link was inserted automatically and has not yet been checked. Please check the original and archive link according to the instructions and then remove this notice. @1@ 2Template: Webachiv / IABot / diditwith.net
  11. ^ Lambda functions. Retrieved October 17, 2015 .
  12. ^ Anonymous functions. Retrieved May 19, 2015 .
  13. ^ The Closure class. Retrieved May 19, 2015 .
  14. ^ Joe Watkin, Phil Sturgeon: Anonymous Classes. September 22, 2013, accessed May 19, 2015 .