Kas yra „uždaryti“?

Aš uždaviau klausimą dėl kario ir uždarymo. Kas yra uždarymas? Kaip tai siejasi su currying?

289
31 авг. Ben nustatytas rugpjūčio 31 d 2008-08-31 07:38 '08, 07:38, 2008-08-31 07:38
@ 15 atsakymų

Kintamas regionas

Kai deklaruojate vietinį kintamąjį, šis kintamasis turi apimtį. Paprastai vietiniai kintamieji egzistuoja tik bloko ar funkcijos viduje, kuriame juos deklaruojate.

 function() { var a = 1; console.log(a); // works } console.log(a); // fails 

Jei bandau pasiekti vietinį kintamąjį, dauguma kalbų ją ieškos dabartiniame regione, o tada per tėvų regionus, kol jie pasiekia šaknų regioną.

 var a = 1; function() { console.log(a); // works } console.log(a); // works 

Kai vykdomas blokas ar funkcija, jo vietiniai kintamieji nebėra reikalingi ir paprastai išeina iš atminties.

Taigi mes paprastai tikimės, kad darbas bus atliktas.

Uždarymas yra nuolatinė vietinė kintamoji sritis

Uždarymas yra nuolatinė taikymo sritis, kuri taikoma vietiniams kintamiesiems net ir po to, kai kodo vykdymas paliko šį bloką. Kalbos, kurios palaiko uždarymą (pvz., „JavaScript“, „Swift“ ir „Ruby“), leis jums išsaugoti nuorodą į taikymo sritį (įskaitant jos pagrindines sritis), net ir po to, kai buvo užblokuotas šis kintamieji, jei išsaugosite nuorodą į koks blokas ar funkcija yra kažkur.

Apimties objektas ir visi jo vietiniai kintamieji yra susieti su funkcija ir bus išsaugoti, kol ši funkcija bus išsaugota.

Tai leidžia mums perkelti funkcijas. Galime tikėtis, kad bet kokie kintamieji, kurie buvo aprėpti, kai funkcija buvo pirmą kartą apibrėžta, vis dar išlieka, kai vėliau vadiname šią funkciją, net jei mes šią funkciją vadiname visiškai kitokiame kontekste.

Pavyzdžiui

Čia yra tikrai paprastas „JavaScript“ pavyzdys, iliustruojantis tašką:

 outer = function() { var a = 1; var inner = function() { console.log(a); } return inner; // this returns a function } var fnc = outer(); // execute outer to get inner fnc(); 

Čia apibrėžiau funkciją. Vidinė funkcija pasiekia visus vietinės išorinės funkcijos kintamuosius, įskaitant a . Kintamasis a yra vidinės funkcijos srityje.

Paprastai, kai funkcija išeina, visi jo vietiniai kintamieji nustatomi iš naujo. Tačiau, jei grąžiname vidinę funkciją ir priskiriame jam fnc kintamąjį taip, kad jis būtų išsaugotas po outer išėjimo, taip pat bus išsaugoti visi kintamieji, kurie buvo regione, kai inner buvo apibrėžtas . Kintamasis a buvo uždarytas - jis yra grandinės viduje.

Atkreipkite dėmesį, kad kintamasis a visiškai uždarytas fnc . Tai būdas sukurti privačius kintamuosius funkcinėje programavimo kalba, pvz., „JavaScript“.

Kaip galbūt spėjote, kai aš kviečiu fnc() , jis išspausdina a reikšmę, kuri yra lygi "1".

Kalboje be uždarymo kintamasis a būtų renkamas ir išmestas šiukšlių, kai išeina iš outer funkcijos. Kvietimas į „fnc“ sukeltų klaidą, nes nebėra.

„JavaScript“ kintamasis a išsaugomas, nes kintamasis domenas sukuriamas, kai funkcija pirmą kartą paskelbiama ir išlieka tol, kol funkcija išlieka.

a reiškia outer lauką. inner yra pagrindinis rodyklė į outer . fnc yra kintamasis, fnc inner . a išlieka, kol fnc išliks. a yra grandinės viduje.

435
19 сент. atsakymas yra suteiktas 19- ojo aukšto lygio . 2011-09-19 00:04 '11 prie 0:04 2011-09-19 00:04

Pateiksiu pavyzdį (javascript):

 function makeCounter () { var count = 0; return function () { count += 1; return count; } } var x = makeCounter(); x(); returns 1 x(); returns 2 ...etc... 

Tai, ką ši makeCounter funkcija atlieka, grąžina funkciją, kurią mes vadiname „x“, kuri bus skaičiuojama po vieną kiekvieną kartą, kai jis vadinamas. Kadangi x nenurodome jokių parametrų, jis turi kažkaip prisiminti rezultatą. Jis žino, kur jį rasti, remiantis vadinamąja leksine sritimi - jis turi pažvelgti į vietą, kurioje yra apibrėžta prasmė. Ši „paslėpta“ vertė vadinama uždarymu.

Čia yra mano currying pavyzdys:

 function add (a) { return function (b) { return a + b; } } var add3 = add(3); add3(4); returns 7 

Tai, ką galite matyti, yra tai, kad kai skambinate pridedant parametrą (kuris yra 3), ši vertė yra grįžimo funkcijos, kurią mes apibrėžiame kaip priedą3, pabaigoje. Taigi, kai mes vadiname add3, jis žino, kur rasti vertę, kurią reikia atlikti.

74
31 авг. Kyle Cronin atsakymas, rugpjūčio 31 d 2008-08-31 07:49 '08 at 7:49 am 2008-08-31 07:49

Kyle atsakymas yra gana geras. Manau, kad vienintelis papildomas paaiškinimas yra tai, kad uždarymas iš esmės yra kamino momentinė nuotrauka toje vietoje, kur sukurta lambda funkcija. Tada, kai funkcija vėl vykdoma, prieš vykdydama funkciją, stekas grįžta į šią būseną. Taigi, kaip nurodo Kyle, ši paslėpta vertė ( count ) yra pasiekiama, kai vykdoma lambda funkcija.

49
31 авг. Ben Childs rugpjūčio 31 d. Atsakymas 2008-08-31 12:08 '08 12:08 val. 2008-08-31 12:08

Uždaryti yra funkcija, kuri gali būti susijusi su kita funkcija. Pavyzdžiui, „Python“ naudoja vidinį uždarymą:

 def outer (a): b = "variable in outer()" def inner (c): print a, b, c return inner # Now the return value from outer() can be saved for later func = outer ("test") func (1) # prints "test variable in outer() 1 
25
31 авг. John Millikin atsakymas rugpjūčio 31 d 2008-08-31 07:54 '08, 07:54, 2008-08-31 07:54

Siekiant palengvinti uždarymo supratimą, būtų naudinga ištirti, kaip jas galima įgyvendinti procedūrine kalba. Šis paaiškinimas apims supaprastintą grandinės uždarymo įgyvendinimą.

Norėdami pradėti, turiu įvesti vardų erdvės koncepciją. Kai įvedate komandą Schemos vertėjas, jis turi įvertinti įvairius simbolius ir gauti jų vertę. Pavyzdys:

 (define x 3) (define y 4) (+ xy) returns 7 

Apibrėžiamosios išraiškos išsaugo vertę 3 vietoje x ir reikšmę 4 y vietoje. Tada, kai mes vadiname (+ xy), vertėjas žiūri į vardų erdvėje esančias vertes ir gali atlikti operaciją ir grąžinti 7.

Tačiau schemoje yra išraiškų, leidžiančių laikinai panaikinti simbolio vertę. Štai pavyzdys:

 (define x 3) (define y 4) (let ((x 5)) (+ xy)) returns 9 x returns 3 

Ką leidžia raktinis žodis reiškia naują vardų sritį su x kaip 5 vertę. Jūs pastebėsite, kad jis vis dar gali matyti, kad y yra 4, kuris grąžina sumą 9. Taip pat galite pamatyti, kad po to, kai išraiška baigta x sugrįžta į 3 būseną. Šiuo atžvilgiu x laikinai užmaskuoja vietinė vertė.

Procesinės ir į objektą orientuotos kalbos yra panašios. Kai deklaruojate kintamąjį funkcijoje, kurios pavadinimas yra toks pat kaip ir pasaulinis kintamasis, jūs gaunate tą patį efektą.

Kaip jį įgyvendinti? Paprastas būdas yra susietas sąrašas - galvoje yra nauja vertė, o uodega turi seną vardų erdvę. Kai jums reikia ieškoti simbolio, pradėsite galvą ir nueikite išilgai uodegos.

Dabar kreipiamės į pirmos klasės funkcijų įgyvendinimą. Daugiau ar mažiau funkcija yra komandų rinkinys, kurį reikia atlikti, kai funkcija vadinama grįžimo vertės kulminacija. Kai skaitome funkciją, mes galime laikyti šias instrukcijas užkulisiuose ir paleisti jas, kai funkcija vadinama.

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns ? 

Nustatome x kaip 3, o plius-x jo parametrą y, pridėjus x vertę. Galiausiai, mes vadiname plius-x aplinkoje, kurioje x buvo užmaskuotas naujuoju x, tai yra apskaičiuota 5. Jei paprasčiausiai išsaugosime operaciją (+ xy) plius-x funkcijai, nes mes esame x kontekste, bus 5, rezultatas bus 9 Tai vadinama dinamine aprėptimi.

Tačiau schemoje, bendroje Lisp ir daugelyje kitų kalbų yra vadinama leksinė taikymo sritis - be operacijos saugojimo (+ xy), taip pat saugome vardų erdvę tam tikru momentu. Taigi, kai žiūrime į vertybes, matome, kad x šiame kontekste yra tikrai 3. Tai yra uždarymas.

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns 7 

Taigi, mes galime naudoti susietą sąrašą, kad išsaugotume vardų erdvės būseną funkcijų apibrėžimo metu, o tai leidžia mums pasiekti kintamuosius iš uždaromų zonų, taip pat suteikti mums galimybę slėpti kintamąjį vietoje, nepaveikiant likusios programos dalies.

23
31 авг. Kyle Cronin atsakymas, rugpjūčio 31 d 2008-08-31 19:30 '08, 19:30, 2008-08-31 19:30

Visų pirma, priešingai nei daugelis žmonių sako, uždarymas nėra funkcija ! Taigi, kas tai?
Tai simbolių rinkinys, apibrėžtas funkcijoje „aplinkinis kontekstas“ (žinomas kaip jo aplinka), kuris tampa CLOSED išraiška (tai reiškia, kad kiekvienas simbolis yra apibrėžtas ir turi vertę, todėl jis gali būti įvertintas).

Pavyzdžiui, kai turite „JavaScript“ funkciją:

 function closed(x) { return x + 3; } 

Tai yra uždara išraiška, nes jame yra apibrėžti visi jame esantys simboliai (jų vertės yra aiškios), todėl galite ją įvertinti. Kitaip tariant, jis yra savarankiškas.

Bet jei turite šią funkciją:

 function open(x) { return x*y + 3; } 

Tai atvira išraiška, nes jame yra simbolių, kurie jame nėra apibrėžti. Būtent, y . Atsižvelgiant į šią funkciją, negalime pasakyti, kas y ir ką reiškia, mes nežinome jo prasmės, todėl negalime įvertinti šios išraiškos. Tai yra, mes negalime vadinti šios funkcijos, kol nepasakysime, ką reiškia. Šis y yra vadinamas laisvu kintamuoju.

Tam reikia apibrėžimo, tačiau ši apibrėžtis nėra funkcijos dalis - ji yra apibrėžta kitur, „aplinkiniame kontekste“ (taip pat žinoma kaip aplinka). Bent jau tikimės: P

Pavyzdžiui, jis gali būti apibrėžtas visame pasaulyje:

 var y = 7; function open(x) { return x*y + 3; } 

Arba tai galima apibrėžti funkcijoje, kuri ją apvynioja:

 var global = 2; function wrapper(y) { var w = "unused"; return function(x) { return x*y + 3; } } 

Aplinkos dalis, suteikianti laisvus kintamuosius jų vertės išraiška, yra uždarymas. Tai vadinama tuo, kad atvira išraiška paverčiama uždarąja, suteikiant šiuos trūkstamus apibrėžimus visiems laisviems kintamiesiems, kad galėtume ją įvertinti.

Anksčiau pateiktame pavyzdyje vidinė funkcija (kurią mes nepateikėme, nes mums to nereikia) yra atvira išraiška, nes kintamasis y jame yra laisvas - jo apibrėžimas neįeina į funkciją, funkciją, kuri ją apvynioja. Šios anoniminės funkcijos aplinka yra kintamųjų rinkinys:

 { global: 2, w: "unused", y: [whatever has been passed to that wrapper function as its parameter `y`] } 

Dabar uždarymas yra šios aplinkos dalis, kuri uždaro vidinę funkciją ir suteikia apibrėžimus visiems laisviesiems kintamiesiems. Mūsų atveju vienintelis laisvas kintamasis vidinėje funkcijoje buvo y , taigi uždarant šią funkciją, šis šios aplinkos pogrupis:

 { y: [whatever has been passed to that wrapper function as its parameter `y`] } 

Kiti du simboliai, apibrėžti aplinkoje, nėra šios funkcijos uždarymas, nes jiems nereikia jų paleisti. Jie neturi uždaryti.

Daugiau apie šią teoriją skaitykite čia: ngn-wiki.ru.site/questions/4901 / ...

Verta pažymėti, kad aukščiau pateiktame pavyzdyje pakuotės funkcija grąžina savo vidinę funkciją kaip vertę. Šiuo metu mes vadiname šią funkciją galima ištrinti laiku nuo to momento, kai funkcija buvo apibrėžta (arba sukurta). Konkrečiai, jo vyniojimo funkcija nebeveikia, o jo parametrai, kurie buvo skambučių stekoje, nebėra: P Tai sukuria problemą, nes vidaus funkcijai reikia, kad ji būtų, kai ji vadinama! Kitaip tariant, norint kažkaip patirti pakuotės funkciją, būtina, kad kintamieji būtų uždaryti ir, jei reikia,. Todėl vidinė funkcija turi fotografuoti šiuos kintamuosius, kurie leidžia juos uždaryti ir saugoti juos vėlesniam naudojimui. (Kažkur už skambučio kamino ribų.)

Taigi žmonės dažnai painioja terminą „uždarymas“ kaip specialią funkciją, kuri gali imtis tokių išorinių kintamųjų, kuriuos jie naudoja, arba duomenų struktūros, naudojamos vėliau laikyti šiuos kintamuosius, vaizdus. Bet tikiuosi, kad dabar jūs suprantate, kad jie nėra pats uždarymas - tai tik būdai, kaip įgyvendinti uždarymą programavimo kalba arba kalbos mechanizmais, kurie leidžia keisti kintamuosius nuo uždarymo funkcijos, jei reikia. Yra daug klaidingų supratimų apie uždarymą, kad (be reikalo) ši tema tampa painesnė ir sudėtingesnė nei iš tikrųjų.

17
27 апр. atsakymas pateikiamas SasQ 27 balandžio. 2016-04-27 05:33 '16 at 5:33 am 2016-04-27 05:33

Štai realaus pasaulio pavyzdys, kodėl uždarymas verčia asilą ... Tai iš mano „JavaScript“ kodo. Leiskite man iliustruoti.

 Function.prototype.delay = function(ms ) { var fn = this, args = Array.prototype.slice.call(arguments, 1); return window.setTimeout(function() { return fn.apply(fn, args); }, ms); }; 

Ir taip jūs naudojate:

 var startPlayback = function(track) { Player.play(track); }; startPlayback(someTrack); 

Įsivaizduokite, kad norite, kad atkūrimas būtų pradėtas, pavyzdžiui, 5 sekundes po šio kodo fragmento. Na, tai yra lengva su delay ir uždarymu:

 startPlayback.delay(5000, someTrack); // Keep going, do other things 

Kai skambinate delay su 5000 ms, pirmasis fragmentas prasideda ir išsaugo perduotus argumentus uždarymo metu. Tada po 5 sekundžių, kai įvyksta „ setTimeout atšaukimas, uždarymas vis tiek išsaugo šiuos kintamuosius, todėl jis gali skambinti pradinei funkcijai su pradiniais parametrais.
Tai yra kario ar dekoravimo funkcija.

Be uždarymo turite kažkaip išlaikyti šiuos kintamuosius už funkcijos ribų, tokiu būdu užblokuojant kodą už funkcijos ribų su tuo, kas logiškai priklauso jo viduje. Uždarų naudojimas gali žymiai pagerinti jūsų kodo kokybę ir suprantamumą.

10
07 нояб. atsakymas adamJLev 07 lapkričio. 2010-11-07 22:04 '10, 10:04 PM 2010-11-07 22:04

Esant normaliai situacijai kintamieji yra saistomi pagal taikymo sritį: vietiniai kintamieji veikia tik konkrečioje funkcijoje. Uždarymas yra būdas laikinai pažeisti šią taisyklę patogumui.

 def n_times(a_thing) return lambda{|n| a_thing * n} end 

aukščiau esančiame kode lambda(|n| a_thing * n} yra artimas, nes a_thing perduodamas lambda (anoniminis funkcijos kūrėjas).

Dabar, jei įdėjote gautą anoniminę funkciją į funkcinį kintamąjį.

 foo = n_times(4) 

foo sulaužys įprastą matomumo taisyklę ir pradės naudoti 4 viduje.

 foo.call(3) 

grąžina 12.

4
31 авг. atsakymą pateikė Eugenijus Yokota 31 d. 2008-08-31 08:31 '08 8:31 AM 2008-08-31 08:31

Funkcijos, kuriose nėra laisvų kintamųjų, vadinamos grynomis funkcijomis.

Funkcijos, turinčios vieną ar daugiau laisvųjų kintamųjų, vadinamos uždarymais.

 var pure = function pure(x){ return x // only own environment is used } var foo = "bar" var closure = function closure(){ return foo // foo is a free variable from the outer environment } 

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-with-free-variables-are-pure-are-closures-impure

3
07 февр. Soundyogi atsakymas 07 Feb 2016-02-07 16:38 '16, 16:38 pm 2016-02-07 16:38

Trumpai tariant, funkcijų rodyklė yra tiesiog rodyklė į vietos kodą programoje (pvz., Programos skaitiklis). Uždarymas = funkcijos rodyklė + kamino rėmas .

.

2
07 сент. Atsakymas, kurį pateikė RoboAlex 07 rugsėjis 2013-09-07 16:50 '13, 16:50, 2013-09-07 16:50

TL; DR

Uždarymas yra funkcija ir jos apimtis, paskirtas (arba naudojamas) kintamasis. Taigi pavadinimo uždarymas: apimtis ir funkcija yra įdėtos ir naudojamos taip pat, kaip ir bet kuris kitas objektas.

Išsamus Wikipedia stiliaus aprašymas

Pasak Vikipedijos, uždaroma :

Leksiškai sujungtos kalbos vardo, susijusio su pirmos klasės funkcijomis, vardo įvedimo metodai.

Ką tai reiškia? Pažvelkime į kai kurias apibrėžtis.

Šiame pavyzdyje paaiškinsiu uždarymus ir kitus susijusius apibrėžimus:

pirmos klasės piliečiai , taigi ir pavadinimas: pirmos klasės funkcijos. 

Pirmiau pateiktame pavyzdyje startAt grąžina funkciją ( anoniminę ), kurios funkcijos priskiriamos closure1 ir closure2 . Kaip matote, „JavaScript“ rankenos veikia kaip ir bet kuris kitas subjektas (pirmos klasės piliečiai).

Pavadinimas privalomas

Pavadinimas privalomas norint sužinoti, kokie duomenys yra nuorodų kintamasis (identifikatorius). Čia taikymo sritis yra tikrai svarbi, nes tai lemia, kaip bus panaikintas privalomasis ryšys.

Pirmiau pateiktame pavyzdyje:

  • Vidaus anoniminės funkcijos srityje y susietas su 3 .
  • startAt x susietas su 1 arba 5 (priklausomai nuo grandinės).

Anoniminės funkcijos viduje x nėra susieta su jokia verte, todėl ji turi būti startAt viršutinėje srityje ( startAt ).

Leksinis regiono apibrėžimas

Kaip sako Vikipedija , taikymo sritis yra:

Ar kompiuterio programos sritis, kurioje yra privalomas privalumas: kai pavadinimas gali būti naudojamas nuorodai į įmonę .

Yra du būdai:

  • Leksinė (statinė) apibrėžtis: kintamojo apibrėžtis išsprendžiama ieškant jo turinio bloko ar funkcijos, o tada, jei ji nepavyksta ieškoti išorinio bloko ir pan.
  • Dinaminis zonos aptikimas: atliekama skambinimo funkcijos paieška, tada funkcija, skambinanti skambinimo funkcijai ir pan.

Norėdami gauti išsamesnį paaiškinimą, perskaitykite šį klausimą ir pažiūrėkite į „Wikipedia“ .

Pirmiau pateiktame pavyzdyje matome, kad „JavaScript“ yra ribota leksika, nes kai x įjungtas, viršutinėje srityje ( startAt ) atliekamas privalomas paieška pagal pradinį kodą (anoniminė funkcija, kuri ieško x, yra nustatyta startAt ) ir nėra pagrįsta steku skambučiai, kelias (apimtis), kuriame funkcija buvo vadinama.

Vyniojimas (uždarymas)

В нашем примере, когда мы вызываем startAt , он будет возвращать (первоклассную) функцию, которая будет назначена closure1 и closure2 , поэтому создается замыкание, поскольку переданные переменные 1 и 5 будет сохранен в области startAt , который будет заключен в возвращаемую анонимную функцию. Когда мы вызываем эту анонимную функцию через closure1 и closure2 с тем же аргументом ( 3 ), значение y будет найдено немедленно (так как это параметр этой функции), но x не связанный в области анонимной функции, поэтому разрешение продолжается в (лексически) верхней области функций (которая была сохранена в закрытии), где x оказывается связанным либо с 1 , либо с 5 . Теперь мы знаем все для суммирования, чтобы результат можно было вернуть, а затем напечатать.