Co je to uzavření?

Každou chvíli vidím zmínku o „uzávěrách“ a zkusil jsem to vyhledat, ale Wiki neposkytuje vysvětlení, kterému rozumím. Mohl by někdo pomozte mi tady?

Komentáře

  • Pokud znáte prostředí Java / C #, doufám, že tento odkaz pomůže – http://www.developerfusion.com/article/8251/the-beauty-of-closures/
  • Uzávěry je těžké pochopit. Zkuste zkusit kliknout na všechny odkazy v první větě tohoto článku na Wikipedii a porozumět těm články jako první.
  • stackoverflow.com/questions/36636/what-is-a-closure
  • co ‚ s tím zásadním rozdílem mezi uzavřením a třídou? Dobře, třída pouze s jednou veřejnou metodou.
  • @biziclop: Mohl bys Napodobuji uzávěr pomocí třídy (‚ co musí vývojáři Java dělat). Ale ‚ jsou obvykle o něco méně upřímní vytvořit a don nemusíte ručně spravovat to, o co se ‚ staráte. (Tvrdé lípy kladou podobnou otázku, ale pravděpodobně dospějí k tomuto druhému závěru – že podpora OO na jazykové úrovni je zbytečná, pokud máte uzávěry.)

Odpověď

(Zřeknutí se odpovědnosti: toto je základní vysvětlení; pokud jde o definici, trochu to zjednodušuji)

Nejjednodušší způsob, jak uvažovat o uzavření, je funkce , kterou lze uložit jako proměnnou (dále jen „první -class funkce „), která má speciální schopnost přistupovat k jiným proměnným lokálně v rozsahu, ve kterém byla vytvořena.

Příklad (JavaScript):

 var setKeyPress = function(callback) { document.onkeypress = callback; }; var initialize = function() { var black = false; document.onclick = function() { black = !black; document.body.style.backgroundColor = black ? "#000000" : "transparent"; } var displayValOfBlack = function() { alert(black); } setKeyPress(displayValOfBlack); }; initialize();  

Funkce 1 přiřazené k document.onclick a displayValOfBlack jsou uzávěry. Vidíte, že oba odkazují na booleovskou proměnnou black, ale tato proměnná je přiřazena mimo funkci. Protože black je místní v oboru, kde byla funkce definována , ukazatel na tuto proměnnou je zachován.

Pokud to vložíte na stránku HTML:

  1. Kliknutím změníte barvu na černou
  2. Stisknutím klávesy [Enter] zobrazíte hodnotu „true“
  3. Opětovným kliknutím změníte barvu zpět na bílou
  4. Stisknutím [Enter] zobrazíte „false“

To ukazuje, že oba mají přístup ke stejnému black, a lze jej použít k uložení stavu bez jakéhokoli objektu wrapper.

Volání setKeyPress má ukázat, jak lze předat funkci jako každá proměnná. obor zachovaný v uzávěru je stále ten, kde byla funkce definována.

Uzávěry jsou obvykle používá se jako obslužné rutiny událostí, zejména v JavaScriptu a ActionScriptu. Dobré použití uzávěrů vám pomůže implicitně vázat proměnné na obslužné rutiny událostí, aniž byste museli vytvářet obálky objektů. Neopatrné použití však povede k úniku paměti (například když je nepoužívaná, ale zachovaná obslužná rutina události jedinou věcí, která se udrží na velkých objektech v paměti, zejména objektech DOM, a brání tak uvolňování paměti).


1: Ve skutečnosti jsou všechny funkce v JavaScriptu zavírací.

Komentáře

  • Jak jsem četl vaši odpověď, cítil, jak se mi v mysli rozsvítila žárovka. Vysoce ceněné! 🙂
  • Vzhledem k tomu, že black je deklarován uvnitř funkce, nedojde k jeho ‚ zničení při odvíjení zásobníku. ..?
  • @ gablin, to je to, co je jedinečné v jazycích, které mají závěry. Všechny jazyky se sběrem odpadků fungují téměř stejným způsobem – pokud nejsou na objekt uchovávány žádné další odkazy, může být zničen. Kdykoli je vytvořena funkce v JS, místní obor je vázán na tuto funkci, dokud není tato funkce zničena.
  • @gablin, to je ‚ dobrá otázka. Nemyslím si ‚, že nemohou ‚ t & mdash; ale vychovával jsem odpadky jen od té doby, co používá JS a že ‚ je to, na co se zdálo, že odkazuje, když jsi řekl “ od black je deklarován uvnitř funkce, nebyl by ‚ t zničen „. Nezapomeňte také, že pokud deklarujete objekt ve funkci a poté jej přiřadíte proměnné, která žije někde jinde, tento objekt se zachová, protože na něj existují další odkazy.
  • Objective-C (a C pod clang) podporuje bloky, které jsou v podstatě uzávěry, bez odvozu odpadu. Vyžaduje runtime podporu a nějaký manuální zásah kolem správy paměti.

Odpověď

Uzávěr je v podstatě jen odlišný způsob pohledu na objekt. Objekt jsou data, která mají k sobě vázanou jednu nebo více funkcí. Uzávěr je funkce, která má k sobě vázanou jednu nebo více proměnných. Oba jsou v zásadě identické, přinejmenším na úrovni implementace. Skutečný rozdíl je v tom, odkud pocházejí.

V objektově orientovaném programování deklarujete třídu objektu definováním jejích členských proměnných a jejích metod (členské funkce) předem a potom vytvoříte instance ta třída. Každá instance je dodávána s kopií dat člena, inicializovanou konstruktorem. Pak máte proměnnou typu objektu a předáte ji jako část dat, protože důraz je kladen na její povahu jako dat.

V závěru naopak objekt není definované předem jako třída objektu nebo vytvořené prostřednictvím volání konstruktoru ve vašem kódu. Místo toho zapíšete uzávěr jako funkci uvnitř jiné funkce. Uzávěr může odkazovat na kteroukoli z místních proměnných vnější funkce a kompilátor to zjistí a přesune tyto proměnné z prostoru zásobníku vnější funkce do deklarace skrytého objektu uzavření. Pak máte proměnnou typu uzavření , a přestože je to v podstatě objekt pod kapotou, předáte jej jako referenci funkce, protože důraz je kladen na její povahu jako funkce.

Komentáře

  • +1: dobrá odpověď. Uzávěr můžete vidět jako objekt pouze s jednou metodou a libovolný objekt jako soubor uzávěrů nad některými běžnými základními daty (členské proměnné objektu ‚). Myslím, že tyto dva pohledy jsou celkem symetrické.
  • Velmi dobrá odpověď. Ve skutečnosti to vysvětluje pohled na uzavření.
  • @Mason Wheeler: Kde jsou uložena data o uzavření? V zásobníku jako funkce? Nebo v haldě jako objekt?
  • @RoboAlex: V haldě, protože ‚ je objekt, který vypadá jako funkce .
  • @RoboAlex: Místo, kde je uložen uzávěr a jeho zachycená data, závisí na implementaci. V C ++ jej lze uložit na haldě nebo na zásobníku.

Odpověď

Termín uzavření pochází ze skutečnosti, že část kódu (blok, funkce) může mít volné proměnné, které jsou closed (tj. vázaný na hodnotu) prostředím, ve kterém je blok kódu definován.

Vezměme si například definici funkce Scala :

def addConstant(v: Int): Int = v + k 

V těle funkce jsou dvě jména (proměnné) v a k označující dvě celočíselné hodnoty. Název v je vázán, protože je deklarován jako argument funkce addConstant (při pohledu na deklaraci funkce víme, že v bude při vyvolání funkce přiřazena hodnota). Název k je pro funkci addConstant zdarma, protože tato funkce nemá ponětí, jaká hodnota k je vázán na (a jak).

Abychom mohli vyhodnotit hovor jako:

val n = addConstant(10) 

musíme přiřadit k hodnota, ke které může dojít, pouze pokud je název k definován v kontextu, ve kterém addConstant je definován. Například:

def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) } 

Nyní jsme definovali addConstant v kontextu, kde k je definováno, addConstant se stalo uzávěrem , protože všechny jeho volné proměnné jsou nyní uzavřeny (vázané na hodnotu): addConstant can být vyvolán a předán, jako by to byla funkce. Všimněte si, že volná proměnná k je vázána na hodnotu, když je uzávěr definován , zatímco proměnná argumentu v je vázána, když je uzávěr vyvolán .

Uzávěr je tedy v zásadě funkční blok nebo blok kódu, který má přístup k nelokálním hodnotám prostřednictvím svých volných proměnných poté, co byly vázány kontextem.

V mnoha jazycích, pokud použít uzávěr pouze jednou, můžete jej anonymní , např.

def increaseAll(values: List[Int]): List[Int] = { val k = 2 values.map(v => v + k) } 

Všimněte si, že funkce bez volných proměnných je speciální případ uzavření (s prázdnou sadou volných proměnných). Analogicky je anonymní funkce zvláštní případ anonymního uzavření , tj. anonymní funkce je anonymní uzávěrka bez volných proměnných.

Komentáře

  • Toto se dobře hodí k logickým uzavřeným i otevřeným vzorcům. Děkujeme za odpověď.
  • @RainDoctor: Volné proměnné jsou definovány v logických vzorcích a ve výrazech lambda kalkulu podobným způsobem: lambda ve výrazu lambda funguje jako kvantifikátor v logických vzorcích wrt volné / vázané proměnné .

Odpověď

Jednoduché vysvětlení v JavaScriptu:

var closure_example = function() { var closure = 0; // after first iteration the value will not be erased from the memory // because it is bound with the returned alertValue function. return { alertValue : function() { closure++; alert(closure); } }; }; closure_example(); 

alert(closure) použije dříve vytvořenou hodnotu closure. Obor názvů vrácené alertValue bude připojen k oboru názvů, ve kterém se nachází proměnná closure. Když odstraníte celou funkci, hodnota proměnné closure bude odstraněna, ale do té doby bude funkce alertValue vždy schopna číst / zapisovat hodnotu proměnné closure.

Pokud spustíte tento kód, přiřadí první iterace proměnné closure hodnotu 0 a přepsat funkci na:

var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } } 

A protože alertValue potřebuje lokální proměnnou closure k provedení funkce se váže s hodnotou dříve přiřazené místní proměnné closure.

A nyní pokaždé, když zavoláte closure_example funkce, vypíše zvýšenou hodnotu proměnné closure, protože alert(closure) je svázáno.

closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc. 

Komentáře

  • děkuji, ‚ t otestovat kód =) vše se nyní zdá být v pořádku.

odpověď

„uzavření“ je , v podstatě nějaký místní stát a nějaký kód, zkombinované do balíčku. Místní stav obvykle pochází z obklopujícího (lexikálního) rozsahu a kód je (v podstatě) vnitřní funkcí, která se poté vrací ven. Uzávěr je pak kombinací zachycených proměnných, které vnitřní funkce vidí, a kódu vnitřní funkce.

Je to jedna z těch věcí, která je bohužel trochu obtížně vysvětlitelná kvůli být neznámý.

Jednou analogií, kterou jsem v minulosti úspěšně používal, bylo „představte si, že máme něco, čemu říkáme„ kniha “, v uzavření místnosti,„ kniha “je ta kopie tam, v rohu , TAOCP, ale na konci tabulky je to ta kopie knihy Drážďanské soubory. Takže podle toho, do jaké uzávěry jste, kód „dej mi knihu“ má za následek různé věci. “

Komentáře

  • Zapomněli jste toto: en.wikipedia.org/wiki/Closure_(computer_programming) ve vaší odpovědi.
  • Ne, vědomě jsem se rozhodl tuto stránku nezavřít.
  • “ Stav a funkce. „: Lze funkci C s static místní proměnnou považovat za uzavření? v Haskellu zahrnout stav?
  • @Giorgio Uzávěry v Haskellu uzavírají (domnívám se) přes argumenty v lexikálním rozsahu, které ‚ definují v, takže já ‚ řekněme “ ano “ (i když v nejlepším případě neznám Haskell). Funkce AC se statickou proměnnou je v nejlepším případě velmi omezené uzavření (opravdu chcete mít možnost vytvořit více uzávěrů z jedné funkce, s místní proměnnou static máte přesně jeden).
  • Tuto otázku jsem položil záměrně, protože si myslím, že funkce C se statickou proměnnou není uzávěr: statická proměnná je definována lokálně a je známa pouze uvnitř uzávěru, nemá přístup prostředí. Také si nejsem 100% jistý, ale tvrzení bych formuloval opačně: mechanismus uzavření použijete k vytvoření různých funkcí (funkce je definice uzavření + vazba pro její volné proměnné).

Odpověď

Je těžké definovat, co je uzávěrka, aniž bychom definovali pojem „stát“.

V zásadě V jazyce s plným lexikálním rozsahem, který považuje funkce jako hodnoty první třídy, se stane něco zvláštního. Pokud bych měl udělat něco jako:

function foo(x) return x end x = foo 

Proměnná x nejen odkazy function foo() ale také odkazuje na stav foo, který byl ponechán při posledním návratu. Skutečné kouzlo nastane, když foo má v rámci své působnosti další funkce; je to jako jeho vlastní mini-prostředí (stejně jako „normálně“ definujeme funkce v globálním prostředí).

Funkčně dokáže vyřešit mnoho stejných problémů jako C ++ (C?) Klíčové slovo „s“ static „, které zachovává stav lokální proměnné během několika volání funkcí; ale je to spíš jako použít stejný princip (statickou proměnnou) na funkci, protože funkce jsou hodnotami první třídy; uzavření přidává podporu pro uložení stavu celé funkce (nic společného se statickými funkcemi C ++).

Zacházení s funkcemi jako s hodnotami první třídy a přidávání podpory pro uzávěry také znamená, že v paměti můžete mít více než jednu instanci stejné funkce (podobně jako u tříd). To znamená, že můžete znovu použít stejný kód, aniž byste museli resetovat stav funkce, jak je vyžadováno při práci se statickými proměnnými C ++ uvnitř funkce (může se v tom mýlit?).

Zde je několik testů podpory uzavření Luy .

--Closure testing --By Trae Barlow -- function myclosure() print(pvalue)--nil local pvalue = pvalue or 10 return function() pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed) print(pvalue) pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls) return pvalue end end x = myclosure() --x now references anonymous function inside myclosure() x()--nil, 20 x() --21, 31 x() --32, 42 --43, 53 -- if we iterated x() again 

výsledky:

nil 20 31 42 

Může to být složité a pravděpodobně se to bude lišit z jazyka do jazyka, ale v Lua se zdá, že kdykoli je funkce spuštěna, její stav je resetován. Říkám to proto, že výsledky z výše uvedeného kódu by se lišily, kdybychom přistupovali k funkce / stav přímo (namísto anonymní funkce se vrací), protože pvalue by byl resetován zpět na 10; ale pokud přistoupíme ke stavu myclosure prostřednictvím x (anonymní funkce), můžete vidět, že pvalue je někde v paměti naživu a dobře. Mám podezření, že je toho trochu víc někdo může lépe vysvětlit podstatu implementace.

PS: Neznám lízání C ++ 11 (kromě toho, co je v předchozích verzích), takže mějte na paměti, že toto není srovnání mezi uzávěry v C ++ 11 a Lua. Také všechny „čáry nakreslené“ z Lua do C ++ jsou podobnosti, protože statické proměnné a uzávěry nejsou 100% stejné; i když se někdy používají k řešení podobných problémů.

To, čím si nejsem jistý, je, že v příkladu kódu výše je za uzavření považována anonymní funkce nebo funkce vyššího řádu?

Odpověď

Uzávěr je funkce, která má přidružený stav:

V Perlu vytváříte uzávěry takto:

#!/usr/bin/perl # This function creates a closure. sub getHelloPrint { # Bind state for the function we are returning. my ($first) = @_;a # The function returned will have access to the variable $first return sub { my ($second) = @_; print "$first $second\n"; }; } my $hw = getHelloPrint("Hello"); my $gw = getHelloPrint("Goodby"); &$hw("World"); // Print Hello World &$gw("World"); // PRint Goodby World 

Podíváme-li se na novou funkcionalitu poskytovanou v C ++.
Umožňuje také svázat aktuální stav s objektem:

#include <string> #include <iostream> #include <functional> std::function<void(std::string const&)> getLambda(std::string const& first) { // Here we bind `first` to the function // The second parameter will be passed when we call the function return [first](std::string const& second) -> void { std::cout << first << " " << second << "\n"; }; } int main(int argc, char* argv[]) { auto hw = getLambda("Hello"); auto gw = getLambda("GoodBye"); hw("World"); gw("World"); } 

Odpověď

Zvažme jednoduchou funkci:

function f1(x) { // ... something } 

Tato funkce se nazývá funkce nejvyšší úrovně, protože není vnořena do žádné jiné funkce. Každý Funkce JavaScriptu k sobě přidruží seznam objektů zvaných “ Rozsah oboru „. Tento řetězec rozsahu je uspořádaný seznam objektů ach těchto objektů definuje některé proměnné.

Ve funkcích nejvyšší úrovně se řetězec oboru skládá z jediného objektu, globálního objektu. Například výše uvedená funkce f1 má řetězec oboru, který obsahuje jeden objekt, který definuje všechny globální proměnné. (Všimněte si, že výraz „objekt“ zde neznamená objekt JavaScriptu, je to jen objekt definovaný implementací, který funguje jako kontejner proměnných, ve kterém může JavaScript „vyhledávat“ proměnné.)

Když toto funkce je vyvolána, JavaScript vytvoří něco, co se nazývá „Aktivační objekt“ , a umístí jej na začátek řetězce oboru. objekt obsahuje všechny místní proměnné (například x zde). Proto nyní máme v řetězci rozsahu dva objekty: první je aktivační objekt a pod ním globální objekt.

Všimněte si velmi opatrně, že tyto dva objekty jsou vloženy do řetězce oboru v RŮZNÝCH časech. Globální objekt je vložen, když je funkce definována (tj. když JavaScript analyzoval funkci a vytvořil objekt funkce), a aktivační objekt vstoupí, když je funkce vyvolána.

Takže nyní to víme:

  • Každá funkce má k sobě přidružený řetězec oboru
  • Kdyžfunkce je definována (když je vytvořen objekt funkce), JavaScript uloží řetězec oboru s touto funkcí
  • U funkcí nejvyšší úrovně obsahuje řetězec rozsahu pouze globální objekt v době definice funkce a přidá další aktivační objekt nahoře v době vyvolání

Situace se stane zajímavou, když se budeme zabývat vnořenými funkcemi. Pojďme tedy vytvořit jeden:

function f1(x) { function f2(y) { // ... something } } 

Když je f1 definován, dostaneme pro něj řetězec oboru obsahující pouze globální objekt.

Nyní, když je volán f1, získá řetězec aktivace f1 aktivační objekt. Tento aktivační objekt obsahuje proměnnou x a proměnnou f2, která je funkcí. A nezapomeňte, že f2 se definuje.Proto v tomto okamžiku JavaScript také uloží nový řetězec rozsahu pro f2. Řetězec oboru uložený pro tuto vnitřní funkci je aktuální řetězec oboru v platnosti. Aktuální obor řetěz ve skutečnosti je řetěz f1 „s. f2 proto řetěz rozsahu je f1 „s aktuální rozsah řetězce – který obsahuje aktivační objekt f1 a globální objekt.

Když se volá f2, získá to vlastní aktivační objekt obsahující y, přidán do svého řetězce rozsahu, který již obsahuje aktivační objekt f1 a globální objekt.

Pokud byla v rámci f2, jeho rozsah oboru by obsahoval tři objekty v době definice (2 aktivační objekty dvou vnějších funkcí a globální objekt) a 4 v době vyvolání.

Takže, teď podtržíme jak funguje řetězec rozsahu, ale o uzavřeních jsme zatím nemluvili.

Kombinace funkčního objektu a oboru (sada proměnných vazeb) ve kterém jsou vyřešeny proměnné funkce, se v počítačové informatice nazývá uzávěr – JavaScript, definitivní průvodce Davida Flanagana

Většina funkcí je vyvolána pomocí stejného řetězce rozsahu, který platil při definování funkce, a nezáleží na tom, zda se jedná o uzavření. Uzávěry se stanou zajímavými, když jsou vyvolány v jiném řetězci rozsahu než v tom, který platil při jejich definování. K tomu dochází nejčastěji, když je vnořený funkční objekt vrácen z funkce, ve které byl definován.

Když se funkce vrátí, je tento aktivační objekt odebrán z řetězce oboru. Pokud nebyly žádné vnořené funkce, neexistují žádné další odkazy na aktivační objekt a shromažďují se odpadky. Pokud byly definovány vnořené funkce, pak každá z těchto funkcí má odkaz na řetězec oboru a tento řetězec rozsahu odkazuje na aktivační objekt.

Pokud tyto objekty vnořených funkcí zůstaly v rámci své vnější funkce, pak budou sami shromážděni odpadky spolu s aktivačním objektem, na který odkazovali. Ale pokud funkce definuje vnořenou funkci a vrátí ji nebo ji někde uloží do vlastnosti, pak bude existovat externí odkaz na vnořenou funkci. Nebude shromážděno odpadky a aktivační objekt, na který odkazuje, nebude také shromažďováno.

V našem výše uvedeném příkladu nevracíme f2 from f1, tedy když se volání f1 vrátí, jeho aktivační objekt bude odstraněn z jeho řetězce rozsahu a shromážděny odpadky. Pokud bychom ale měli něco takového:

function f1(x) { function f2(y) { // ... something } return f2; } 

Vracející se f2 zde bude mít řetězec oboru, který bude obsahovat aktivační objekt f1, a proto nebude shromažďován odpad. V tomto okamžiku, pokud zavoláme f2, bude mít přístup k proměnné f1x i když jsme mimo f1.

Proto vidíme, že funkce udržuje řetězec rozsahu s ním a řetězcem rozsahu přijdou všechny aktivační objekty vnějších funkcí. To je podstata uzavření. Říkáme, že funkce v JavaScriptu jsou „lexikálně vymezeny“ , což znamená, že ukládají rozsah, který byl aktivní, když byly definovány, na rozdíl od rozsahu, který byl aktivní, když byly volány.

Existuje řada výkonných programovacích technik, které zahrnují uzávěry, jako je přibližování soukromých proměnných , programování řízené událostmi, částečná aplikace atd.

Všimněte si, že toto vše platí pro všechny jazyky, které podporují uzavření. Například PHP (5.3+), Python, Ruby atd.

Odpověď

Uzávěrka je optimalizace kompilátoru (aka syntaktický cukr?). Někteří lidé to označují také jako Objekt chudáka .

Podívejte se na odpověď Erica Lipperta : (výňatek níže)

Kompilátor vygeneruje kód takto:

 private class Locals { public int count; public void Anonymous() { this.count++; } } public Action Counter() { Locals locals = new Locals(); locals.count = 0; Action counter = new Action(locals.Anonymous); return counter; }  

Dává to smysl?
Požádali jste také o srovnání. VB i JScript vytvářejí uzávěry téměř stejným způsobem.

Komentáře

  • Tato odpověď je CW, protože si ‚ nezasloužím body za Erica ‚ skvělá odpověď.Hlasujte prosím, jak uznáte za vhodné. HTH
  • -1: Vaše vysvětlení je v C # příliš rootované. Uzávěr se používá v mnoha jazycích a je v těchto jazycích mnohem víc než syntaktický cukr a zahrnuje jak funkci, tak stav.
  • Ne, uzavření není jen “ optimalizace kompilátoru “ ani syntaktický cukr. -1

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *