Kompiuterių moksle uždarymas (angl. closure) – tai funkcija, kuri kartu saugo savo aplinką. Aplinka yra rinkinys susietų kintamųjų (vardų su reikšmėmis), kuriuos funkcija gali pasiekti net ir tada, kai ta funkcija yra vykdoma kitame kontekste nei ten, kur ji buvo sukurta. Uždaromosios struktūros aplinka saugo susietus kintamuosius atmintyje tarp uždaromosios struktūros naudojimo atvejų, todėl uždarymas gali „įsiminti“ tam tikras reikšmes.
1964 m. Peteris J. Landinas šią idėją pavadino uždarymu. Scheme programavimo kalba išpopuliarino uždarymą po 1975 m. Daugelis po to sukurtų programavimo kalbų turi uždarymų arba bent jau jų analogus.
Anoniminės funkcijos (funkcijos be vardo) kartais klaidingai vadinamos uždarymo funkcijomis. Daugumoje kalbų, kuriose yra anoniminių funkcijų, taip pat yra uždarymų. Anoniminė funkcija taip pat yra uždarymas, jei ji turi savo aplinką su bent vienu susietu kintamuoju. Anoniminė funkcija, neturinti savo aplinkos, nėra uždarymas. Įvardyta uždarymo funkcija nėra anoniminė.
Kaip veikia uždarymai
Paprastai uždarymas susidaro tada, kai funkcija apibrėžiama viduje kito bloko ar funkcijos ir naudoja aplinkos kintamuosius iš išorinio bloko. Svarbu atskirti dvi sąvokas:
- Lexical (stebėjimo) scoping – funkcijos reikšmės susiejamos su aplinka tuo metu, kai funkcija yra apibrėžiama.
- Dynamic scoping – kintamųjų reikšmės surandamos pagal vykdymo vietą; dauguma populiarių kalbų (JavaScript, Python, Scheme) naudoja lexicelį scope, todėl uždarymai veikia apibrėžimo metu.
Pavyzdžiai (sintaksės iliustracija)
Štai paprastas JavaScript pavyzdys, demonstruojantis, kaip funkcija „įsimena“ kintamąjį:
function makeCounter() { let count = 0; return function() { count += 1; return count; }; } const c = makeCounter(); c(); // 1 c(); // 2 Funkcija grąžinama iš makeCounter ir toliau turi prieigą prie vietinio kintamojo count. Dėl to kiekvienas tokios fabriko funkcijos iškvietimas gali turėti savo atskirą būseną.
Python pavyzdys:
def make_adder(x): def add(y): return x + y return add add5 = make_adder(5) add5(3) # 8
Naudojimo sritys
- Duomenų kapsuliavimas ir privatumas (pavyzdžiui, slėpti vietinius kintamuosius nuo išorinio pasaulio).
- Atgaliniai kvietimai (callbacks) ir asinchroninis programavimas – uždarymai leidžia palaikyti būseną tarp kvietimų.
- Funkcijų fabrikai (factory functions) – sukurti specializuotas funkcijas parametrizuojant uždaromosios aplinkos kintamuosius.
- Dalinis taikymas (partial application) ir funkcijų komponavimas.
Įgyvendinimo ir atminties aspektai
Įgyvendinant uždarymus, kalbų vykdymo aplinka turi užtikrinti, kad tie kintamieji, kuriuos uždarymas naudoja, išliktų gyvi tiek, kiek reikia. Kai kuriose įgyvendinimuose vietinės funkcijų „rėmeliai“ perkeliami iš steko į šiukšlių rinkimo valdomą heap atmintį, kad reikšmės nepradingtų pasibaigus išoriniam funkcijos kvietimui. Dėl to gali padidėti atminties naudojimas; prastai valdomi uždarymai gali lemti netyčinius atminties nutekėjimus (pvz., laikant dideles struktūras ilgą laiką).
Optimizacijos, kurias naudoja kompiliatoriai/arbitražai:
- Lambda lifting – perkelti uždarymams reikalingus kintamuosius į funkcijos parametrus.
- Escape analysis – nuspręsti, ar vietinis kintamasis gali likti steke arba turi būti alokuotas heap'e.
Dažnos klaidos ir spąstai
- Uždarymai gali netyčia „pagauti“ kintamuosius, kurie vėliau keičiasi (pavyzdžiui, senesnės JavaScript versijos su var ir ciklais). Sprendimas – naudoti let arba sukurti vidinę funkcinę kopiją kintamojo.
- Bandymas seralizuoti uždarymus paprastai neveikia, nes uždarymo aplinka gali būti sudėtinga arba turėti nuorodas į išorinius išteklius.
- Netinkamas uždaromųjų naudojimas gali apsunkinti atminties valdymą ir sudėtingumą bandant suprasti programos būseną.
Kada uždarymas YRA ir KADA NĖRA
Ne kiekviena funkcija yra uždarymas. Funkcija bus uždarymas tik tada, kai ji turi susietą aplinką su bent vienu kintamuoju, kurio reikšmė nėra vietinė tik tuo metu vykdant funkciją. Iš kitos pusės, anoniminė funkcija tampa uždarymu tik tuomet, kai ji iš tikrųjų pagavo (naudojasi) kintamuoju iš išorinės aplinkos. Įvardinta funkcija, kuri pagavo aplinkos kintamuosius, taip pat yra uždarymas – vardas neteikia ar neatima uždarymo savybių.
Trumpas santrauka
Uždarymai yra galingas konceptas programavimo kalbose, leidžiantis funkcijoms ne tik vykdyti kodą, bet ir išsaugoti būseną per laiko tarpą. Jie plačiai naudojami funkcinėje ir imperatyvioje paradigmoje, tačiau reikalauja supratimo apie scope, atminties valdymą ir galimas spąstus. Žinojimas, kada ir kaip uždarymai veikia, padeda rašyti aiškesnį, saugesnį ir efektyvesnį kodą.