W informatyce, zamknięcie (closure) jest funkcją, która posiada własne środowisko. W tym środowisku znajdują się co najmniej jedna lub więcej zmiennych związanych (tzw. zmienne wolne), których wartości są „zapamiętywane” i dostępne dla funkcji nawet po zakończeniu wykonania zakresu, w którym te zmienne zostały zadeklarowane. Innymi słowy, domknięcie to funkcja razem ze zbiorem powiązanych wartości kontekstowych, które funkcja widzi i może modyfikować.
Termin „closure” został wprowadzony przez Petera J. Landina w 1964 roku. Język programowania Scheme spopularyzował domknięcia po 1975 roku, a dziś wiele współczesnych języków programowania je obsługuje (np. JavaScript, Python, Ruby, Swift, Kotlin, Rust). Domknięcia są kluczowe w programowaniu funkcyjnym i w technikach opartych na funkcjach pierwszej klasy.
Jak działają domknięcia
- Domknięcie powstaje, gdy funkcja (wewnętrzna) odwołuje się do zmiennych zewnętrznego zakresu (leksykalnego) — te zmienne stają się częścią środowiska domknięcia.
- Środowisko domknięcia przechowuje wartości tych zmiennych w pamięci tak długo, jak długo istnieje odniesienie do funkcji (np. przez zwrócenie funkcji lub przypisanie jej do zmiennej).
- Domknięcia respektują leksykalny (statyczny) zakres: zmienne wiązane są według miejsca deklaracji w kodzie, nie według miejsca wywołania.
Różnica między funkcją anonimową a domknięciem
Funkcje anonimowe (funkcje bez nazwy) bywają mylnie utożsamiane z domknięciami. Warto podkreślić różnicę:
- Funkcja anonimowa to po prostu funkcja bez nazwy. Może, ale nie musi, być domknięciem.
- Funkcja jest domknięciem wtedy i tylko wtedy, gdy posiada własne środowisko z co najmniej jedną zmienną związaną — niezależnie od tego, czy ma nazwę.
- Nazwane zamknięcie (funkcja z nazwą, która tworzy domknięcie) dalej jest domknięciem, jeśli ma własne środowisko.
Przykłady
Przykład w JavaScript (licznik):
function makeCounter() { let count = 0; return function() { count++; return count; }; } const c = makeCounter(); console.log(c()); // 1 console.log(c()); // 2 W tym przykładzie zwrócona funkcja „zapamiętuje” zmienną count z otaczającego zakresu — dzięki temu stan jest utrzymywany między wywołaniami.
Przykład w Pythonie (użycie nonlocal):
def make_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter c = make_counter() print(c()) # 1 print(c()) # 2 W Pythonie, aby modyfikować zmienną zewnętrzną, trzeba użyć słowa kluczowego nonlocal (w przypadku zmiennej w bezpośrednim zewnętrznym zakresie) lub global dla zmiennej globalnej.
Typowe pułapki
- Przechwytywanie zmiennych w pętli: w niektórych językach (np. starsze idiomy JS z var) wszystkie funkcje mogą przechwycić tę samą zmienną iteracyjną, co prowadzi do nieoczekiwanych wyników. Rozwiązaniem jest tworzenie nowych wiązań (np. użycie let w ES6 lub przekazanie wartości jako parametr domyślny).
- Pamięć: domknięcia mogą utrzymywać duże struktury danych w pamięci dłużej niż oczekiwano, ponieważ referencje do zmiennych zewnętrznych zapobiegają ich zwolnieniu przez garbage collector. Należy uważać na niezamierzone utrzymywanie stanu.
- Serializacja i debugowanie: domknięć nie da się zwykle łatwo zserializować (np. wysłać przez sieć), bo zawierają stan i kod. Debugowanie może być trudniejsze ze względu na ukryty stan.
Zastosowania
- Enkapsulacja i prywatny stan: tworzenie funkcji fabrykujących obiekty z ukrytym stanem (np. modułowy pattern).
- Factory functions i generatorów: konstruowanie funkcji skonfigurowanych z parametrami zachowanymi w domknięciu.
- Kallbacki i programowanie asynchroniczne: przekazywanie funkcji, które mają dostęp do wcześniejszych zmiennych kontekstowych.
- Funkcje wyższego rzędu: zwracanie funkcji spersonalizowanych pod kątem wcześniejszych argumentów.
Podsumowanie
Domknięcia to mechanizm pozwalający funkcjom przechowywać i używać stanu spoza ich lokalnego zakresu dzięki uchwyceniu zmiennych leksykalnych. Są potężnym narzędziem do organizowania kodu, ukrywania stanu i tworzenia elastycznych abstrakcji. Należy jednak pamiętać o typowych pułapkach związanych z zarządzaniem pamięcią i specyfiką wiązań zmiennych w danym języku.