Затваряне в информатиката — дефиниция, механизъм и примери
Затваряне в информатиката: ясна дефиниция, практичен механизъм и илюстративни примери. Разберете как работят средите и анонимните функции.
В информатиката затварянето е функция, която има собствена среда. В тази среда има поне една обвързана променлива (име, което има стойност, например число). Средата на затварянето съхранява обвързаните променливи в паметта между употребите на затварянето.
През 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, за да освободят неизползваните среди, но ако затварянето е все още референцирано (например чрез глобална променлива или дълго живеещ обект), средата няма да бъде събрана.
Обобщение
Затварянето е мощна концепция, която позволява функции да запомнят и използват контекст от момента на тяхното създаване. Те са полезни за инкапсулация, конфигурирани функции и асинхронни модели. Разбирането на това как затварянето съхранява средата и какъв е жизненият ѝ цикъл помага да се избегнат често срещани грешки като неочаквано споделяне на състояние или паметни изтичания.
Затваряне и първокласни функции
Стойностите могат да бъдат числа или друг вид данни, например букви, или структури от данни, съставени от по-прости части. В правилата на един език за програмиране стойностите от първи клас са стойности, които могат да се дават на функции, да се връщат от функции и да се свързват с име на променлива. Функциите, които приемат или връщат други функции, се наричат функции от по-висок клас. Повечето езици, в които функциите са стойности от първи клас, имат и функции от по-висок ред и затваряния.
Вижте например следната функция на схемата:
В този пример ламбда изразът (ламбда (book) (>= (book-sales book) threshold)) е част от функцията best-selling-books. Когато функцията се изпълнява, Scheme трябва да направи стойността на ламбда израза. Тя прави това, като създава затваряне с кода на ламбдата и препратка към променливата threshold, която е свободна променлива вътре в ламбдата. (Свободна променлива е име, което не е обвързано със стойност.)
След това функцията за филтриране изпълнява затварянето на всяка книга в списъка, за да избере кои книги да върне. Тъй като самото затваряне има препратка към прага, затварянето може да използва тази стойност всеки път, когато филтърът изпълнява затварянето. Самата функция филтър може да бъде записана в напълно отделен файл.
Ето същия пример, пренаписан на ECMAScript (JavaScript) - друг популярен език с поддръжка на затваряния:
ECMAScript използва думата function вместо lambda и метода Array.filter вместо функцията filter, но иначе кодът прави същото нещо по същия начин.
Функцията може да създаде затваряне и да го върне. Следващият пример е функция, която връща функция.
В схемата:
В ECMAScript:
Средата на затваряне запазва обвързаните променливи f и dx след връщането на ограждащата функция (производна). В езици без затваряне тези стойности биха били изгубени след връщането на ограждащата функция. В езиците със затваряния обвързаната променлива трябва да се пази в паметта, докато някое затваряне я има.
Не е задължително затварянето да се формира с помощта на анонимна функция. Езикът за програмиране Python например има ограничена поддръжка на анонимни функции, но има затваряния. Например един от начините, по които горният пример на ECMAScript може да бъде реализиран на езика Python, е:
В този пример функцията, наречена gradient, се затваря заедно с променливите f и dx. Външната ограждаща функция, наречена дериватив, връща това затваряне. В този случай би работила и анонимна функция.
Вместо това Python често трябва да използва именувани функции, тъй като неговите ламбда изрази могат да съдържат само други изрази (код, който връща стойност), но не и оператори (код, който има ефекти, но няма стойност). Но в други езици, като Scheme, всички кодове връщат стойност; в Scheme всичко е израз.
Употреби на затворите
Затворите имат много приложения:
- Дизайнерите на софтуерни библиотеки могат да позволят на потребителите да персонализират поведението си, като предават затваряния като аргументи на важни функции. Например функция, която подрежда стойности, може да приеме аргумент за затваряне, който сравнява стойностите, които трябва да бъдат подредени, според определен от потребителя критерий.
- Тъй като затварянето забавя оценката, т.е. не "прави" нищо, докато не бъде извикано, то може да се използва за дефиниране на структури за управление. Например всички стандартни структури за управление на Smalltalk, включително разклонения (if/then/else) и цикли (while и for), са дефинирани с помощта на обекти, чиито методи приемат затваряния. Потребителите могат лесно да дефинират и свои собствени структури за управление.
- Могат да бъдат създадени няколко функции, които се затварят в една и съща среда, което им позволява да общуват частно, като променят тази среда (на езици, които позволяват присвояване).
В схемата
- Затварянията могат да се използват за реализиране на обектни системи.
Забележка: Някои автори наричат всяка структура от данни, която свързва лексикална среда, затваряне, но този термин обикновено се отнася конкретно за функции.
Въпроси и отговори
В: Какво представлява закриването на компютърни науки?
О: Затварянето е функция, която има собствена среда.
Въпрос: Какво съдържа средата на един завършек?
О.: Средата на затваряне съдържа поне една обвързана променлива.
Въпрос: Кой е дал името на идеята за затваряне?
О: Питър Й. Ландин дава името на идеята за затваряне през 1964 г.
Въпрос: Кой език за програмиране направи затворите популярни след 1975 г.?
О: Езикът за програмиране Scheme направи затворите популярни след 1975 г.
Въпрос: Анонимните функции и затварянията едно и също нещо ли са?
О: Анонимните функции понякога погрешно се наричат затваряния, но не всички анонимни функции са затваряния.
В: Какво прави една анонимна функция затваряне?
О: Една анонимна функция е затваряне, ако има собствена среда с поне една обвързана променлива.
Въпрос: Анонимна ли е именувана затваряща функция?
О: Не, именуваният завършек не е анонимен.
обискирам