Closures

2023-01-18

Closures #

Ich spiele gerade ein bisschen mit Go herum, weil ich mich aus meiner funktionalen Ecke heraustrauen und etwas mehr mit imperativen, C-artigen Sprachen befassen möchte. Rust hätte mich auch interessiert, scheint aber ein Fass ohne Boden zu sein. Go ist modern, trotzdem stabil, recht kompakt und weitverbreitet. Die Liste der großen und populären Softwareprojekte, die auf Go setzen, ist beachtlich.

In der Go-Tour gibt es einen Abschnitt zu Closures. Ich nutze die Gelegenheit, um für mich zu klären was Closures sind. Ich hab den Begriff bisher nie wirklich verstanden.

Eine Closure (i.e. ein Funktionsabschluss) ist eine Funkion, die Referenzen auf ihren Erstellungskontext enthält, wobei dieser Kontext außerhalb der Funktion nicht mehr sichtbar und nicht mehr referenzierbar ist.

Ein Beisiel aus dem Haskell Wiki:

> f x = \ y -> x + y
> g = f 3
> g 4
7

Wenn wir g definieren, wird der Wert 3 an den Parameter x gebunden, so dass g = \ y -> 3 + y gilt. Auf den Kontext, in dem die Bindung x = 3 vorliegt, können wir aber nicht mehr zugreifen, obwohl er weiter existiert solange g existiert. Eine Closure liegt vor weil die Bindung x = 3 im Kontext des Lambda-Ausdrucks erfolgt aber innerhalb des Lambda-Ausdrucks verwendet wird. Dadurch lebt der Kontext über die Definition hinaus weiter, weil er für die Auswertung benötigt wird.

Ich verstehe das, aber was ich nicht verstehe ist, warum man dieser Sache einen gesonderten Namen gibt und sie als eine spezielle Programmiertechnik behandelt. Statt f x = \ y -> x + y könnte man auch schreiben: f x y = x + y. Das ist die normalste Sache der Welt und ich verstehe überhaupt nicht, wie es anders sein könnte, also was es bedeuten soll, ohne Closures zu programmieren.

Beim Schreiben kommt mir der Verdacht, dass es irgendwie damit zusammenhängen muss, dass Sprachen wie C keine First Class Functions haben. Wenn man es nicht gewohnt ist, kann das Herumreichen von Funktionen als Argumente und Rückgabewerte mit allen Möglichkeiten, die sich daraus ergeben, vielleicht wie ein besonderer Hokuspokus erscheinen, der erst mal in einen Begriff zu bringen ist: Closure — eine Funktion, die auf freien Variablen operiert.

Das Gegenteil wäre ein Combinator — eine Funktion ohne freie Variable.

Update

Ok, ich hab’s! Der eigentliche Effekt, an dem man offenbar bei der imperativen Programmierung mit Closures interessiert ist, tritt bei der rein funktionalen Programmierung gar nicht auf. Eine Closure ist in diesem Fall eine Prozedur mit einem inneren Zustand, der über mehrere Aufrufe hinweg fortbesteht und veränderbar ist. Hier ein angepasstes Beispiel aus der Go Tour:

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}
func main() {
    f := adder()
    fmt.Println(f(0)) // 0
    fmt.Println(f(1)) // 1
    fmt.Println(f(2)) // 3
    fmt.Println(f(3)) // 6
    fmt.Println(f(4)) // 10
    fmt.Println(f(5)) // 15
}

Was passiert hier? Die Prozedur f hat einen inneren Zustand, der mit sum := 0 initialisiert wird, bei jedem Aufruf von f verändert wird und nur indirekt über den Rückgabewert observierbar ist. Daraus könnte man ein Objektsystem stricken.

Nicht alle imperativen Sprachen unterstützen Closures. Wenn sie es nicht tun, wird f im obigen Beispiel zur Identitätsfunktion, weil f(n) stets zu 0 + n auswertet.