Quest-ce quune fermeture?

De temps en temps, je vois des « fermetures » mentionnées, et jai essayé de chercher mais Wiki ne donne pas une explication que je comprends. Quelquun pourrait-il aidez-moi ici?

Commentaires

  • Si vous connaissez Java / C # espérons que ce lien vous aidera – http://www.developerfusion.com/article/8251/the-beauty-of-closures/
  • Les fermetures sont difficiles à comprendre. Vous devriez essayer de cliquer sur tous les liens de la première phrase de cet article Wikipédia et les comprendre articles en premier.
  • stackoverflow.com/questions/36636/what-is-a-closure
  • Quoi ‘ est la différence fondamentale entre une fermeture et une classe? Daccord, une classe avec une seule méthode publique.
  • @biziclop: Vous pourriez émule une fermeture avec une classe (qui ‘ est ce que les développeurs Java doivent faire). Mais ils ‘ sont généralement un peu moins verbeux pour créer et vous ne t avoir à gérer manuellement ce que vous ‘ transportez. (Les lispers hardcore posent une question similaire, mais arrivent probablement à cette autre conclusion – que le support OO au niveau de la langue est inutile lorsque vous avez des fermetures).

Réponse

(Avertissement: ceci est une explication de base; pour ce qui est de la définition, je simplifie un peu)

La façon la plus simple de penser à une fermeture est une fonction qui peut être stockée sous forme de variable (appelée « dabord -class function « ), qui a une capacité spéciale daccéder à dautres variables locales à la portée dans laquelle elle a été créée.

Exemple (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();  

Les fonctions 1 affectées à document.onclick et displayValOfBlack sont des fermetures. Vous pouvez voir quelles font toutes deux référence à la variable booléenne black, mais cette variable est affectée en dehors de la fonction. Parce que black est local à la portée où la fonction a été définie , le pointeur vers cette variable est conservé.

Si vous mettez ceci dans une page HTML:

  1. Cliquez pour passer au noir
  2. Appuyez sur [entrée] pour voir « vrai »
  3. Cliquez à nouveau, redevient blanc
  4. Appuyez sur [entrée] pour voir « faux »

Cela démontre que les deux ont accès au même black, et peut être utilisé pour stocker létat sans aucun objet wrapper.

Lappel à setKeyPress est de montrer comment une fonction peut être passée comme nimporte quelle variable. La portée conservée dans la fermeture est toujours celle où la fonction a été définie.

Les fermetures sont généralement utilisé comme gestionnaire dévénements, en particulier dans JavaScript et ActionScript. Une bonne utilisation des fermetures vous aidera à lier implicitement des variables aux gestionnaires dévénements sans avoir à créer un wrapper dobjet. Cependant, une utilisation imprudente entraînera des fuites de mémoire (par exemple lorsquun gestionnaire dévénements inutilisé mais préservé est la seule chose à retenir sur les gros objets en mémoire, en particulier les objets DOM, empêchant le garbage collection).


1: En fait, toutes les fonctions de JavaScript sont des fermetures.

Commentaires

  • Alors que je lisais votre réponse, je senti une ampoule sallumer dans mon esprit. Très appréciée! 🙂
  • Puisque black est déclaré à lintérieur dune fonction, ‘ ne serait pas détruit lorsque la pile se déroule. ..?
  • @gablin, cest ce qui est unique dans les langues qui ont des fermetures. Tous les langages avec garbage collection fonctionnent à peu près de la même manière – lorsque plus aucune référence nest conservée à un objet, il peut être détruit. Chaque fois quune fonction est créée dans JS, la portée locale est liée à cette fonction jusquà ce que cette fonction soit détruite.
  • @gablin, cette ‘ est une bonne question. Je ‘ ne pense pas quils peuvent ‘ t & mdash; mais jai seulement évoqué le ramassage des ordures puisque cest ce que JS utilise et que ‘ est ce à quoi vous sembliez faire référence quand vous avez dit  » Depuis black est déclaré dans une fonction, ne ‘ t qui serait détruit « . Souvenez-vous également que si vous déclarez un objet dans une fonction et que vous lassignez ensuite à une variable qui vit ailleurs, cet objet est conservé car il y a dautres références à lui.
  • Objective-C (et C sous clang) prend en charge les blocs, qui sont essentiellement des fermetures, sans garbage collection. Il nécessite un support dexécution et une intervention manuelle autour de la gestion de la mémoire.

Réponse

Une fermeture est simplement une manière différente de regarder un objet. Un objet est une donnée à laquelle une ou plusieurs fonctions sont liées. Une fermeture est une fonction à laquelle une ou plusieurs variables sont liées. Les deux sont fondamentalement identiques, au moins au niveau de la mise en œuvre. La vraie différence réside dans leur provenance.

Dans la programmation orientée objet, vous déclarez une classe dobjets en définissant ses variables membres et ses méthodes (fonctions membres) à lavance, puis vous créez des instances de cette classe. Chaque instance est livrée avec une copie des données du membre, initialisée par le constructeur. Vous avez alors une variable dun type dobjet, et vous la transmettez comme une donnée, car laccent est mis sur sa nature en tant que données.

Dans une fermeture, par contre, lobjet nest pas défini davance comme une classe dobjets, ou instancié via un appel de constructeur dans votre code. Au lieu de cela, vous écrivez la fermeture comme une fonction à lintérieur dune autre fonction. La fermeture peut faire référence à nimporte laquelle des variables locales de la fonction externe, et le compilateur détecte cela et déplace ces variables de lespace de pile de la fonction externe vers la déclaration dobjet caché de la fermeture. Vous avez alors une variable de type fermeture. , et même sil sagit essentiellement dun objet sous le capot, vous le passez comme référence de fonction, car laccent est mis sur sa nature en tant que fonction.

Commentaires

  • +1: Bonne réponse. Vous pouvez voir une fermeture comme un objet avec une seule méthode et un objet arbitraire comme une collection de fermetures sur certaines données sous-jacentes communes (les variables membres de lobjet ‘). Je pense que ces deux points de vue sont assez symétriques.
  • Très bonne réponse. Cela explique en fait la perspicacité de la fermeture.
  • @Mason Wheeler: Où les données de fermeture sont-elles stockées? En pile comme une fonction? Ou en tas comme un objet?
  • @RoboAlex: Dans le tas, car ‘ est un objet qui ressemble à une fonction .
  • @RoboAlex: lemplacement de stockage dune fermeture et de ses données capturées dépend de limplémentation. En C ++, il peut être stocké dans le tas ou sur la pile.

Réponse

Le terme fermeture vient du fait quun morceau de code (bloc, fonction) peut avoir des variables libres qui sont fermé (cest-à-dire lié à une valeur) par lenvironnement dans lequel le bloc de code est défini.

Prenons par exemple la définition de la fonction Scala :

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

Dans le corps de la fonction, il y a deux noms (variables) v et k indiquant deux valeurs entières. Le nom v est lié car il est déclaré comme argument de la fonction addConstant (en regardant la déclaration de fonction, nous savons que v se verra attribuer une valeur lorsque la fonction sera appelée). Le nom k est libre pour la fonction addConstant car la fonction ne contient aucun indice sur la valeur k est lié à (et comment).

Pour évaluer un appel comme:

val n = addConstant(10) 

nous devons attribuer k une valeur, qui ne peut se produire que si le nom k est défini dans le contexte dans lequel addConstant est défini. Par exemple:

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

Maintenant que nous avons défini addConstant dans un contexte où k est défini, addConstant est devenu une fermeture car tout ses variables libres sont désormais fermées (liées à une valeur): addConstant peut être invoquée et transmise comme sil sagissait dune fonction. Notez que la variable libre k est liée à une valeur lorsque la fermeture est définie , alors que la variable dargument v est liée lorsque la fermeture est appelée .

Donc, une fermeture est essentiellement une fonction ou un bloc de code qui peut accéder à des valeurs non locales via ses variables libres après que celles-ci aient été liées par le contexte.

Dans de nombreuses langues, si vous nutilisez une fermeture quune fois que vous pouvez la rendre anonyme , par exemple

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

Notez quune fonction sans variables libres est un cas particulier de fermeture (avec un ensemble vide de variables libres). De manière analogue, une fonction anonyme est un cas particulier de fermeture anonyme , cest-à-dire quune fonction anonyme est une fermeture anonyme sans variables libres.

Commentaires

  • Cela saccorde bien avec les formules fermées et ouvertes en logique. Merci pour votre réponse.
  • @RainDoctor: Les variables libres sont définies dans des formules logiques et dans des expressions de calcul lambda de la même manière: le lambda dans une expression lambda fonctionne comme un quantificateur dans les formules logiques avec des variables libres / liées .

Réponse

Une explication simple en JavaScript:

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) utilisera la valeur précédemment créée de closure. Lespace de noms de la alertValue function « renvoyé sera connecté à lespace de noms dans lequel réside la variable closure. Lorsque vous supprimez la fonction entière, le la valeur de la variable closure sera supprimée, mais dici là, la fonction alertValue pourra toujours lire / écrire la valeur de la variable closure.

Si vous exécutez ce code, la première itération attribuera une valeur 0 à la variable closure et réécrire la fonction en:

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

Et parce que alertValue a besoin de la variable locale closure pour exécuter la fonction, il se lie avec la valeur de la variable locale précédemment affectée closure.

Et maintenant, chaque fois que vous appelez le closure_example, il écrira la valeur incrémentée de la variable closure car alert(closure) est lié.

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

Commentaires

  • merci, je nai pas ‘ t tester le code =) tout semble bien maintenant.

Réponse

Une « fermeture » est , en substance, un état local et un code, combinés dans un package. En règle générale, létat local provient dune portée (lexicale) environnante et le code est (essentiellement) une fonction interne qui est ensuite renvoyée à lextérieur. La fermeture est alors une combinaison des variables capturées que la fonction interne voit et du code de la fonction interne.

Cest une de ces choses qui est, malheureusement, un peu difficile à expliquer, en raison de être inconnu.

Une analogie que jai utilisée avec succès dans le passé était «imaginez que nous avons quelque chose que nous appelons« le livre », dans la fermeture de la pièce,« le livre »est cette copie là, dans le coin , de TAOCP, mais en ce qui concerne la fermeture de table, cest cette copie dun livre Dresden Files. Donc, selon la fermeture dans laquelle vous vous trouvez, le code « donne-moi le livre » entraîne différentes choses. « 

Commentaires

  • Vous avez oublié ceci: en.wikipedia.org/wiki/Closure_(computer_programming) dans votre réponse.
  • Non, jai choisi consciemment de ne pas fermer cette page.
  •  » État et fonction. « : Une fonction C avec une variable locale static peut-elle être considérée comme une fermeture? dans Haskell impliquent létat?
  • @Giorgio Les fermetures dans Haskell se referment (je crois) sur les arguments dans la portée lexicale dans laquelle ils ‘ sont définis, donc, je ‘ d say  » oui  » (bien que je ne connaisse au mieux Haskell). La fonction AC avec une variable statique est, au mieux, une fermeture très limitée (vous voulez vraiment pouvoir créer plusieurs fermetures à partir dune seule fonction, avec une variable locale static, vous avez exactement une).
  • Jai posé cette question exprès car je pense quune fonction C avec une variable statique nest pas une fermeture: la variable statique est définie localement et connue uniquement à lintérieur de la fermeture, elle naccède pas lenvironnement. De plus, je ne suis pas sûr à 100% mais je formulerais votre déclaration dans lautre sens: vous utilisez le mécanisme de fermeture pour créer différentes fonctions (une fonction est une définition de fermeture + une liaison pour ses variables libres).

Réponse

Il est difficile de définir ce quest la fermeture sans définir le concept d « état ».

En gros , dans un langage à portée lexicale complète qui traite les fonctions comme des valeurs de première classe, quelque chose de spécial se produit. Si je devais faire quelque chose comme:

function foo(x) return x end x = foo 

La variable x ne fait pas seulement référence à function foo() mais il fait également référence à létat foo qui a été laissé lors de son dernier retour. La vraie magie se produit lorsque foo a dautres fonctions définies plus avant dans sa portée; cest comme son propre mini-environnement (tout comme « normalement » nous définissons des fonctions dans un environnement global).

Fonctionnellement, il peut résoudre plusieurs des mêmes problèmes que le C ++ (C?) mot-clé « s » static « , qui conserve létat dune variable locale pendant plusieurs appels de fonction; cependant, cela ressemble plus à appliquer le même principe (variable statique) à une fonction, car les fonctions sont des valeurs de première classe; la fermeture ajoute le support de la sauvegarde de létat de la fonction entière (rien à voir avec les fonctions statiques de C ++).

Traiter les fonctions comme des valeurs de première classe et ajouter la prise en charge des fermetures signifie également que vous pouvez avoir plus dune instance de la même fonction en mémoire (similaire aux classes). Cela signifie que vous pouvez réutiliser le même code sans avoir à réinitialiser létat de la fonction, comme cela est nécessaire pour traiter les variables statiques C ++ à lintérieur dune fonction (peut-être se tromper à ce sujet?).

Voici quelques tests du support de fermeture de Lua .

--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 

résultats:

nil 20 31 42 

Cela peut devenir délicat, et cela varie probablement dune langue à lautre, mais il semble en Lua que chaque fois quune fonction est exécutée, son état est réinitialisé. Je dis cela parce que les résultats du code ci-dessus seraient différents si nous accédions au fonction / état directement (au lieu de passer par la fonction anonyme quelle renvoie), car pvalue serait réinitialisé à 10; mais si nous accédons à létat de myclosure via x (la fonction anonyme), vous pouvez voir que pvalue est bien vivant quelque part en mémoire. Je suppose quil y a un peu plus, peut-être quelquun peut mieux expliquer la nature de limplémentation.

PS: Je ne connais pas un coup de langue de C ++ 11 (autre que ce qui est dans les versions précédentes) alors notez que ce nest pas une comparaison entre les fermetures en C ++ 11 et Lua. De plus, toutes les «lignes tracées» de Lua à C ++ sont des similitudes car les variables statiques et les fermetures ne sont pas identiques à 100%; même si elles sont parfois utilisées pour résoudre des problèmes similaires.

La chose dont je ne suis pas sûr est, dans lexemple de code ci-dessus, si la fonction anonyme ou la fonction dordre supérieur est considérée comme la fermeture?

Réponse

Une fermeture est une fonction qui a un état associé:

En perl, vous créez des fermetures comme ceci:

#!/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 

Si nous regardons la nouvelle fonctionnalité fournie avec C ++.
Elle vous permet également de lier létat actuel à lobjet:

#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"); } 

Réponse

Considérons une fonction simple:

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

Cette fonction est appelée une fonction de niveau supérieur car elle « nest imbriquée dans aucune autre fonction. Chaque La fonction JavaScript sassocie à elle-même une liste dobjets appelée  » Scope Chain « . Cette chaîne de portées est une liste ordonnée dobjets. E ach de ces objets définit certaines variables.

Dans les fonctions de niveau supérieur, la chaîne de portée se compose dun seul objet, lobjet global. Par exemple, la fonction f1 ci-dessus a une chaîne de portées qui contient un seul objet qui définit toutes les variables globales. (notez que le terme « objet » ne signifie pas ici un objet JavaScript, cest juste un objet défini par limplémentation qui agit comme un conteneur de variables, dans lequel JavaScript peut « rechercher » des variables.)

Quand ceci est appelée, JavaScript crée quelque chose appelé « Objet dactivation » , et le place en haut de la chaîne de portées. object contient toutes les variables locales (par exemple x ici). Par conséquent, nous avons maintenant deux objets dans la chaîne de portée: le premier est lobjet dactivation et en dessous se trouve lobjet global.

Notez très attentivement que les deux objets sont placés dans la chaîne de portées à des moments DIFFÉRENTS. Lobjet global est placé lorsque la fonction est définie (cest-à-dire lorsque JavaScript a analysé la fonction et créé lobjet fonction), et lobjet dactivation entre lorsque la fonction est appelée.

Donc, nous savons maintenant ceci:

  • Chaque fonction a une chaîne de portée associée
  • Quandla fonction est définie (lors de la création de lobjet fonction), JavaScript enregistre une chaîne de portée avec cette fonction
  • Pour les fonctions de niveau supérieur, la chaîne de portée contient uniquement lobjet global au moment de la définition de la fonction et ajoute un objet dactivation en haut au moment de lappel

La situation devient intéressante lorsque nous traitons des fonctions imbriquées. Alors, créons-en un:

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

Lorsque f1 est défini, nous obtenons une chaîne de portée contenant uniquement lobjet global.

Désormais, lorsque f1 est appelé, la chaîne de portée de f1 obtient lobjet dactivation. Cet objet dactivation contient la variable x et la variable f2 qui est une fonction. Et notez que f2 est en cours de définition.Par conséquent, à ce stade, JavaScript enregistre également une nouvelle chaîne de portées pour f2. La chaîne de portées enregistrée pour cette fonction interne est la chaîne de portées actuelle en vigueur. La portée actuelle la chaîne en vigueur est celle de f1 « s. Par conséquent, la chaîne de portée de f2 » est f1 « s chaîne de portée actuelle – qui contient lobjet dactivation de f1 et lobjet global.

Lorsque f2 est appelé, il « obtient son propre objet dactivation contenant y, ajouté à sa chaîne de portées qui contient déjà lobjet dactivation de f1 et lobjet global.

Sil y avait une autre fonction imbriquée définie dans f2, sa chaîne de portées contiendrait trois objets au moment de la définition (2 objets dactivation de deux fonctions externes et lobjet global), et 4 au moment de lappel.

Donc, maintenant nous sous tand comment fonctionne la chaîne de portée mais nous navons pas encore parlé de fermetures.

La combinaison dun objet fonction et dune portée (un ensemble de liaisons de variables) dans laquelle les variables de la fonction sont résolues est appelée une fermeture dans la littérature informatique – JavaScript le guide définitif de David Flanagan

La plupart des fonctions sont appelées en utilisant la même chaîne de portées qui était en vigueur lorsque la fonction a été définie, et peu importe quil y ait une fermeture impliquée. Les fermetures deviennent intéressantes lorsquelles sont invoquées sous une chaîne de portées différente de celle qui était en vigueur lors de leur définition. Cela se produit le plus souvent lorsquun objet fonction imbriqué est renvoyé à partir de la fonction dans laquelle il a été défini.

Lorsque la fonction revient, cet objet dactivation est supprimé de la chaîne de portée. Sil ny avait pas de fonctions imbriquées, il ny a plus de références à lobjet dactivation et il est récupéré. Sil y avait des fonctions imbriquées définies, alors chacune de ces fonctions a une référence à la chaîne de portées, et cette chaîne de portées fait référence à lobjet dactivation.

Si ces objets de fonctions imbriquées sont restés dans leur fonction externe, cependant, alors ils seront eux-mêmes ramassés, avec lobjet dactivation auquel ils se réfèrent. Mais si la fonction définit une fonction imbriquée et la renvoie ou la stocke dans une propriété quelque part, alors il y aura une référence externe à la fonction imbriquée. Il ne sera pas récupéré et l’objet d’activation auquel il fait référence ne le sera pas non plus.

Dans notre exemple ci-dessus, nous ne renvoyons pas f2 from f1, par conséquent, lorsquun appel à f1 retourne, son objet dactivation sera supprimé de sa chaîne de portée et récupéré. Mais si nous avions quelque chose comme ceci:

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

Ici, le f2 de retour aura une chaîne de portée qui sera contiennent l’objet d’activation de f1, et par conséquent, il ne sera pas «garbage collection». À ce stade, si nous appelons f2, il pourra accéder à la variable de f1 « x même si nous « sommes hors de f1.

Par conséquent, nous pouvons voir quune fonction garde sa chaîne de portée avec elle et avec la chaîne de portée viennent tous les objets dactivation des fonctions externes. Cest lessence même de la fermeture. Nous disons que les fonctions en JavaScript sont « lexically scoped » , ce qui signifie quils sauvegardent la portée qui était active quand ils ont été définis par opposition à la portée qui était active quand ils ont été appelés.

Il existe un certain nombre de techniques de programmation puissantes qui impliquent des fermetures comme lapproximation de variables privées , programmation événementielle, application partielle , etc.

Notez également que tout cela sapplique à tous les langages qui prennent en charge les fermetures. Par exemple PHP (5.3+), Python, Ruby, etc.

Réponse

Une fermeture est une optimisation du compilateur (aka sucre syntaxique?). Certaines personnes lont également appelé Objet du pauvre .

Voir la réponse dEric Lippert : (extrait ci-dessous)

Le compilateur générera du code comme celui-ci:

 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; }  

Ça a du sens?
Vous avez également demandé des comparaisons. VB et JScript créent tous deux des fermetures à peu près de la même manière.

Commentaires

  • Cette réponse est une CW parce que je ne mérite ‘ que des points pour Eric ‘
  • -1: Votre explication est trop racine en C #. La fermeture est utilisée dans de nombreuses langues et est bien plus quun simple sucre syntaxique dans ces langues et englobe à la fois la fonction et létat.
  • Non, une fermeture nest ni simplement un  » optimisation du compilateur  » ni sucre syntaxique. -1

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *