Domknięcie (closure) w programowaniu — definicja, działanie i przykłady

Domknięcie (closure) w programowaniu — jasna definicja, działanie krok po kroku i praktyczne przykłady kodu, które wyjaśnią użycie, różnice z funkcjami anonimowymi i korzyści.

Autor: Leandro Alegsa

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.

Domknięcia i funkcje pierwszej klasy

Wartościami mogą być liczby lub inny typ danych, np. litery, lub struktury danych składające się z prostszych części. W regułach języka programowania, wartościami pierwszej klasy są wartości, które mogą być przekazywane funkcjom, zwracane przez funkcje i wiązane z nazwą zmiennej. Funkcje, które pobierają lub zwracają inne funkcje są nazywane funkcjami wyższego rzędu. Większość języków, które mają funkcje jako wartości pierwszej klasy, ma również funkcje wyższego rzędu i domknięcia.

Na przykład, spójrz na następującą funkcję Scheme:

; Zwróć listę wszystkich książek, które sprzedały się w co najmniej TYSIĄC egzemplarzy. (define (best-selling-books threshold) (filter (lambda (book) (>= (book-sales book) threshold))      book-list))

W tym przykładzie, wyrażenie lambda (lambda (book) (>= (book-sales book) threshold)) jest częścią funkcji best-selling-books. Gdy funkcja jest uruchamiana, Scheme musi utworzyć wartość lambda. Robi to poprzez utworzenie domknięcia z kodem lambdy i referencją do zmiennej threshold, która jest zmienną wolną wewnątrz lambdy. (Zmienna wolna to nazwa, która nie jest związana z wartością).

Funkcja filtru następnie uruchamia zamknięcie na każdej książce z listy, aby wybrać książki do zwrócenia. Ponieważ samo zamknięcie posiada referencję do progu, zamknięcie może używać tej wartości za każdym razem, gdy filter uruchamia zamknięcie. Sama funkcja filter może być napisana w zupełnie osobnym pliku.

Oto ten sam przykład przepisany w ECMAScript (JavaScript), innym popularnym języku z obsługą domknięć:

// Zwróć listę wszystkich książek, które sprzedały się w co najmniej 'progowej' liczbie egzemplarzy. function bestSellingBooks(threshold) { return bookList. filter( function(book) { return book. sales >= threshold; } ); }

ECMAScript używa tutaj słowa function zamiast lambda, oraz metody Array.filter zamiast funkcji filter, ale poza tym kod robi to samo w ten sam sposób.

Funkcja może tworzyć domknięcie i zwracać je. Poniższy przykład jest funkcją, która zwraca funkcję.

W schemacie:

; Zwróć funkcję, która przybliża pochodną f ; używając przedziału dx, który powinien być odpowiednio mały. (define (pochodna f dx) (lambda (x) (/ (- (f (+ x dx))) (f x)) dx)))

W ECMAScript:

// Zwróć funkcję, która przybliża pochodną f // za pomocą przedziału dx, który powinien być odpowiednio mały. function derivative(f, dx) { return function(x) { return (f(x + dx) - f(x)) / dx; }; }

Środowisko domknięcia zachowuje związane zmienne f i dx po powrocie funkcji zamykającej (pochodnej). W językach bez domknięć, wartości te zostałyby utracone po powrocie funkcji zamykającej. W językach z domknięciami, zmienna związana musi być przechowywana w pamięci tak długo, jak długo ma ją dowolne domknięcie.

Domknięcie nie musi być utworzone przy użyciu funkcji anonimowej. Na przykład język programowania Python ma ograniczone wsparcie dla funkcji anonimowych, ale posiada domknięcia. Na przykład, jednym ze sposobów, w jaki powyższy przykład ECMAScript mógłby zostać zaimplementowany w Pythonie jest:

# Zwróć funkcję, która przybliża pochodną f # używając przedziału dx, który powinien być odpowiednio mały. def derivative(f, dx): def gradient(x): return (f(x + dx) - f(x)) / dx return gradient

W tym przykładzie funkcja o nazwie gradient tworzy zamknięcie wraz ze zmiennymi f i dx. Zewnętrzna funkcja o nazwie pochodna zwraca to zamknięcie. W tym przypadku zadziałałaby również funkcja anonimowa.

def derivative(f, dx): return lambda x: (f(x + dx) - f(x)) / dx

Python musi często używać funkcji nazwanych, ponieważ jego wyrażenia lambda mogą zawierać tylko inne wyrażenia (kod, który zwraca wartość), a nie deklaracje (kod, który ma efekty, ale nie ma wartości). Jednak w innych językach, takich jak Scheme, cały kod zwraca wartość; w Scheme wszystko jest wyrażeniem.

Zastosowania domknięć

Zamknięcia mają wiele zastosowań:

  • Projektanci bibliotek programistycznych mogą pozwolić użytkownikom na dostosowanie zachowania poprzez przekazywanie domknięć jako argumentów do ważnych funkcji. Na przykład, funkcja sortująca wartości może przyjąć argument zamknięcia, który porównuje wartości, które mają być posortowane według zdefiniowanego przez użytkownika kryterium.
  • Ponieważ domknięcia opóźniają ewaluację - tzn. nie "robią" niczego dopóki nie zostaną wywołane - mogą być używane do definiowania struktur kontrolnych. Na przykład, wszystkie standardowe struktury kontrolne Smalltalka, włączając w to rozgałęzienia (if/then/else) i pętle (while i for), są zdefiniowane przy użyciu obiektów, których metody akceptują domknięcia. Użytkownicy mogą również łatwo definiować własne struktury kontrolne.
  • Można wyprodukować wiele funkcji, które zamykają się nad tym samym środowiskiem, umożliwiając im prywatną komunikację poprzez zmianę tego środowiska (w językach, które pozwalają na przypisanie).

W programie

(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 "spotkajmy się w dokach o północy") (display (bar)) ; prints "spotkajmy się w dokach o północy"
  • Zamknięcia mogą być używane do implementacji systemów obiektowych.

Uwaga: Niektórzy mówcy nazywają domknięciem dowolną strukturę danych, która wiąże środowisko leksykalne, ale termin ten zwykle odnosi się konkretnie do funkcji.

Pytania i odpowiedzi

P: Co to jest domknięcie w informatyce?


A: Domknięcie to funkcja, która ma swoje własne środowisko.

P: Co zawiera środowisko domknięcia?


A: Środowisko domknięcia zawiera co najmniej jedną zmienną związaną.

P: Kto nadał nazwę idei domknięcia?


O: Peter J. Landin nadał nazwę idei domknięcia w 1964 roku.

P: Który język programowania spopularyzował domknięcia po 1975 roku?


O: Język programowania Scheme uczynił domknięcia popularnymi po 1975 roku.

P: Czy funkcje anonimowe i domknięcia to to samo?


O: Funkcje anonimowe są czasami błędnie nazywane domknięciami, ale nie wszystkie funkcje anonimowe są domknięciami.

P: Co sprawia, że funkcja anonimowa jest domknięciem?


O: Funkcja anonimowa jest zamknięciem, jeżeli posiada własne środowisko z co najmniej jedną zmienną związaną.

P: Czy nazwane zamknięcie jest anonimowe?


O: Nie, nazwane zamknięcie nie jest anonimowe.


Przeszukaj encyklopedię
AlegsaOnline.com - 2020 / 2025 - License CC3