Затваряне в информатиката — дефиниция, механизъм и примери

Затваряне в информатиката: ясна дефиниция, практичен механизъм и илюстративни примери. Разберете как работят средите и анонимните функции.

Автор: Leandro Alegsa

В информатиката затварянето е функция, която има собствена среда. В тази среда има поне една обвързана променлива (име, което има стойност, например число). Средата на затварянето съхранява обвързаните променливи в паметта между употребите на затварянето.

През 1964 г. Питър Джей Ландин дава на тази идея името "затваряне". След 1975 г. езикът за програмиране Scheme прави затварянето популярно. Много езици за програмиране, създадени след това, имат затваряния.

Анонимните функции (функции без име) понякога неправилно се наричат затваряния. Повечето езици, които имат анонимни функции, имат и затваряния. Една анонимна функция също е затваряне, ако има собствена среда с поне една обвързана променлива. Анонимна функция, която няма собствена среда, не е затваряне. Именувана затваряща функция не е анонимна.

Как работи затварянето

В основата си затварянето свързва функция с контекста (средата), в която тя е дефинирана. Тази среда съдържа стойности на локални променливи и параметри. Когато затварянето се извика по-късно, то все още има достъп до тези променливи, дори ако извикващият код вече е приключил изпълнението си.

Типичен механизъм включва следните елементи:

  • Дефиниране на функция вътре в друга функция: вътрешната функция "вижда" локалните променливи на външната.
  • Съхранение на средата: изпълнителната система (runtime) запазва необходимата информация в паметта, така че тя да не бъде освободена при излизане от външната функция.
  • Достъп при по-късни извиквания: затварянето използва съхранените стойности, когато се извиква извън оригиналния контекст.

Лексично (статично) и динамично обхващане

Важно е да се различава как се намират обвързаните променливи:

  • Лексично (статично) обхващане: връзките между променливи и функции се определят при компилация/четене на кода според неговата структура. Повечето съвременни езици (JavaScript, Python, Scheme) използват лексично обхващане.
  • Динамично обхващане: променливите се намират според текущия стек от извиквания по време на изпълнение. Това е по-рядко срещано и може да причини различно поведение на затварянията.

Примери

Примери помагат да се види практическата употреба. По-долу има схематични примери на популярни езици:

JavaScript (лексично обхващане):

function makeCounter() {   let count = 0;   return function() {     count += 1;     return count;   }; } const counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2

Python (функции като обекти):

def make_counter():     count = 0     def counter():         nonlocal count         count += 1         return count     return counter  c = make_counter() print(c())  # 1 print(c())  # 2

Scheme (класическа среда за затваряния):

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

Приложения и полезни случаи

Затварянията се използват често за:

  • Инкапсулация на състояние: създаване на частни променливи, достъпни само през функции (мини-обекти без класове).
  • Фабрики за функции: генериране на персонализирани функции с предварително зададени параметри.
  • Колбекове и асинхронен код: предаване на функции, които запазват контекст за по-късно изпълнение.
  • Декоратори и частично приложение: модифициране на поведение чрез обвиване на функции.

Чести обърквания и грешки

  • Анонимни функции ≠ затваряния: както беше споменато, анонимната функция може да не е затваряне, ако не записва или няма нужда от външен контекст.
  • Променливи в цикли: при създаване на функции в цикъл в някои езици могат да възникнат неочаквани резултати, ако всички функции споделят една и съща външна променлива. Решенията включват създаване на нова локална променлива във всяка итерация или използване на лексически блокове.
  • Паметни изтичания: затварянията задържат референции към средата, което може да предотврати освобождаването ѝ от garbage collector и да доведе до увеличена паметна консумация, ако не се внимава.

Памет и жизнен цикъл

Когато затварянето продължи да бъде достъпно, средата му също остава в паметта. Много runtime среди използват garbage collection, за да освободят неизползваните среди, но ако затварянето е все още референцирано (например чрез глобална променлива или дълго живеещ обект), средата няма да бъде събрана.

Обобщение

Затварянето е мощна концепция, която позволява функции да запомнят и използват контекст от момента на тяхното създаване. Те са полезни за инкапсулация, конфигурирани функции и асинхронни модели. Разбирането на това как затварянето съхранява средата и какъв е жизненият ѝ цикъл помага да се избегнат често срещани грешки като неочаквано споделяне на състояние или паметни изтичания.

Затваряне и първокласни функции

Стойностите могат да бъдат числа или друг вид данни, например букви, или структури от данни, съставени от по-прости части. В правилата на един език за програмиране стойностите от първи клас са стойности, които могат да се дават на функции, да се връщат от функции и да се свързват с име на променлива. Функциите, които приемат или връщат други функции, се наричат функции от по-висок клас. Повечето езици, в които функциите са стойности от първи клас, имат и функции от по-висок ред и затваряния.

Вижте например следната функция на схемата:

; Връщане на списък с всички книги с поне ТРИ хиляди продадени копия. (define (best-selling-books threshold) (filter (lambda (book) (>= (book-sales book) threshold)) book-list))

В този пример ламбда изразът (ламбда (book) (>= (book-sales book) threshold)) е част от функцията best-selling-books. Когато функцията се изпълнява, Scheme трябва да направи стойността на ламбда израза. Тя прави това, като създава затваряне с кода на ламбдата и препратка към променливата threshold, която е свободна променлива вътре в ламбдата. (Свободна променлива е име, което не е обвързано със стойност.)

След това функцията за филтриране изпълнява затварянето на всяка книга в списъка, за да избере кои книги да върне. Тъй като самото затваряне има препратка към прага, затварянето може да използва тази стойност всеки път, когато филтърът изпълнява затварянето. Самата функция филтър може да бъде записана в напълно отделен файл.

Ето същия пример, пренаписан на ECMAScript (JavaScript) - друг популярен език с поддръжка на затваряния:

// Връщане на списък с всички книги с продадени поне 'праг' копия. function bestSellingBooks(threshold) { return bookList. filter( function(book) { return book. sales >= threshold; }     ); }

ECMAScript използва думата function вместо lambda и метода Array.filter вместо функцията filter, но иначе кодът прави същото нещо по същия начин.

Функцията може да създаде затваряне и да го върне. Следващият пример е функция, която връща функция.

В схемата:

; Връщане на функция, която приближава производната на f ; използвайки интервал от dx, който трябва да е подходящо малък. (define (derivative f dx) (lambda (x) (/ (- (f (+ x dx)) (f x)) dx)))

В ECMAScript:

// Връщане на функция, която апроксимира производната на f // с помощта на интервал от dx, който трябва да е подходящо малък. функция derivative(f, dx) { return function(x) { return (f(x + dx) - f(x)) / dx; }; }

Средата на затваряне запазва обвързаните променливи f и dx след връщането на ограждащата функция (производна). В езици без затваряне тези стойности биха били изгубени след връщането на ограждащата функция. В езиците със затваряния обвързаната променлива трябва да се пази в паметта, докато някое затваряне я има.

Не е задължително затварянето да се формира с помощта на анонимна функция. Езикът за програмиране Python например има ограничена поддръжка на анонимни функции, но има затваряния. Например един от начините, по които горният пример на ECMAScript може да бъде реализиран на езика Python, е:

# Върнете функция, която апроксимира производната на f # с помощта на интервал от dx, който трябва да е подходящо малък. def derivative(f, dx): def gradient(x): return (f(x + dx) - f(x)) / dx return gradient

В този пример функцията, наречена gradient, се затваря заедно с променливите f и dx. Външната ограждаща функция, наречена дериватив, връща това затваряне. В този случай би работила и анонимна функция.

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

Вместо това Python често трябва да използва именувани функции, тъй като неговите ламбда изрази могат да съдържат само други изрази (код, който връща стойност), но не и оператори (код, който има ефекти, но няма стойност). Но в други езици, като Scheme, всички кодове връщат стойност; в Scheme всичко е израз.

Употреби на затворите

Затворите имат много приложения:

  • Дизайнерите на софтуерни библиотеки могат да позволят на потребителите да персонализират поведението си, като предават затваряния като аргументи на важни функции. Например функция, която подрежда стойности, може да приеме аргумент за затваряне, който сравнява стойностите, които трябва да бъдат подредени, според определен от потребителя критерий.
  • Тъй като затварянето забавя оценката, т.е. не "прави" нищо, докато не бъде извикано, то може да се използва за дефиниране на структури за управление. Например всички стандартни структури за управление на Smalltalk, включително разклонения (if/then/else) и цикли (while и for), са дефинирани с помощта на обекти, чиито методи приемат затваряния. Потребителите могат лесно да дефинират и свои собствени структури за управление.
  • Могат да бъдат създадени няколко функции, които се затварят в една и съща среда, което им позволява да общуват частно, като променят тази среда (на езици, които позволяват присвояване).

В схемата

(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"
  • Затварянията могат да се използват за реализиране на обектни системи.

Забележка: Някои автори наричат всяка структура от данни, която свързва лексикална среда, затваряне, но този термин обикновено се отнася конкретно за функции.

Въпроси и отговори

В: Какво представлява закриването на компютърни науки?


О: Затварянето е функция, която има собствена среда.

Въпрос: Какво съдържа средата на един завършек?


О.: Средата на затваряне съдържа поне една обвързана променлива.

Въпрос: Кой е дал името на идеята за затваряне?


О: Питър Й. Ландин дава името на идеята за затваряне през 1964 г.

Въпрос: Кой език за програмиране направи затворите популярни след 1975 г.?


О: Езикът за програмиране Scheme направи затворите популярни след 1975 г.

Въпрос: Анонимните функции и затварянията едно и също нещо ли са?


О: Анонимните функции понякога погрешно се наричат затваряния, но не всички анонимни функции са затваряния.

В: Какво прави една анонимна функция затваряне?


О: Една анонимна функция е затваряне, ако има собствена среда с поне една обвързана променлива.

Въпрос: Анонимна ли е именувана затваряща функция?


О: Не, именуваният завършек не е анонимен.


обискирам
AlegsaOnline.com - 2020 / 2025 - License CC3