De temps en temps, je vois des "fermetures" mentionnées, et j'ai essayé de les rechercher, mais Wiki ne donne pas d'explication que je comprends. Quelqu'un pourrait-il m'aider ici?
De temps en temps, je vois des "fermetures" mentionnées, et j'ai essayé de les rechercher, mais Wiki ne donne pas d'explication que je comprends. Quelqu'un pourrait-il m'aider ici?
Réponses:
(Avertissement: ceci est une explication de base; en ce qui concerne la définition, je simplifie un peu)
Le moyen le plus simple de penser à une fermeture est une fonction qui peut être stockée en tant que variable (appelée "fonction de première classe"), qui a une capacité spéciale d'accéder à d'autres variables locales à l'étendue 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 assignées à document.onclick
et displayValOfBlack
sont des fermetures. Vous pouvez voir qu'ils font tous les deux référence à la variable booléenne black
, mais cette variable est affectée en dehors de la fonction. Comme il black
est local à la portée où la fonction a été définie , le pointeur sur cette variable est préservé.
Si vous mettez ceci dans une page HTML:
Cela montre que les deux ont le même accès black
et peuvent être utilisés pour stocker l’état sans objet encapsuleur.
L'appel à setKeyPress
est pour montrer comment une fonction peut être passée comme n'importe quelle variable. La portée conservée dans la fermeture est toujours celle où la fonction a été définie.
Les fermetures sont couramment utilisées comme gestionnaires d'événements, notamment 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 d'objet. Cependant, une utilisation négligente entraînera des fuites de mémoire (par exemple, lorsqu'un gestionnaire d'événements inutilisé mais préservé est la seule chose à conserver pour les objets volumineux en mémoire, notamment les objets DOM, empêchant ainsi le garbage collection).
1: En fait, toutes les fonctions en JavaScript sont des fermetures.
black
est déclaré dans une fonction, cela ne serait-il pas détruit lorsque la pile se déroulera ...?
black
est déclaré dans une fonction, cela ne serait-il pas détruit". Rappelez-vous également que si vous déclarez un objet dans une fonction, puis l'affectez à une variable qui se trouve ailleurs, cet objet est préservé car il contient d'autres références.
Une fermeture est fondamentalement juste une façon 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 origine.
En programmation orientée objet, vous déclarez une classe d'objet en définissant ses variables de membre et ses méthodes (fonctions de membre) en amont, puis vous créez des instances de cette classe. Chaque instance est accompagnée d'une copie des données du membre, initialisée par le constructeur. Vous avez alors une variable de type d'objet que vous transmettez sous forme de donnée, car l'accent est mis sur sa nature en tant que donnée.
En revanche, dans une fermeture, l'objet n'est pas défini au préalable comme une classe d'objet, ni instancié via un appel de constructeur dans votre code. Au lieu de cela, vous écrivez la fermeture en tant que fonction dans une autre fonction. La fermeture peut faire référence à l'une des variables locales de la fonction externe. Le compilateur le détecte et déplace ces variables de l'espace de pile de la fonction externe vers la déclaration d'objet masqué de la fermeture. Vous avez alors une variable de type fermeture, et même s'il s'agit essentiellement d'un objet situé sous le capot, vous la transmettez en tant que référence de fonction, car l'accent est mis sur sa nature en tant que fonction.
Le terme de fermeture vient du fait qu'un morceau de code (bloc, fonction) peut avoir des variables libres qui sont fermées (c'est-à-dire liées à une valeur) par l'environnement 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 existe deux noms (variables) v
et k
indiquant deux valeurs entières. Le nom v
est lié car il est déclaré en tant qu'argument de la fonction addConstant
(en regardant la déclaration de fonction, nous savons qu'une valeur v
sera attribuée lorsque la fonction est appelée). Le nom k
est libre par rapport à la fonction addConstant
car celle-ci ne contient aucune indication sur la valeur k
liée (et comment).
Afin d'évaluer un appel comme:
val n = addConstant(10)
nous devons assigner k
une valeur, ce 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 clôture car toutes ses variables libres sont maintenant fermées (liées à une valeur): addConstant
peuvent être invoquées et transmises comme s'il s'agissait d'une fonction. Notez que la variable libre k
est liée à une valeur lorsque la fermeture est définie , alors que la variable d'argument v
est liée lorsque la fermeture est invoquée .
Ainsi, une fermeture est fondamentalement une fonction ou un bloc de code qui peut accéder à des valeurs non locales via ses variables libres après que celles-ci ont été liées par le contexte.
Dans de nombreuses langues, si vous utilisez une fermeture une seule fois, vous pouvez la rendre anonyme , par exemple:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
Notez qu'une 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. En d'autres termes , une fonction anonyme est une fermeture anonyme sans variables libres.
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
. L' alertValue
espace de noms de la fonction retournée sera connecté à l'espace de noms dans lequel closure
réside la variable. Lorsque vous supprimez l'intégralité de la fonction, la valeur de la closure
variable sera supprimée, mais jusqu'à cette date, la alertValue
fonction sera toujours en mesure de lire / écrire la valeur de la variable closure
.
Si vous exécutez ce code, la première itération attribue la valeur 0 à la closure
variable et réécrit la fonction pour:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
Et comme il a alertValue
besoin de la variable locale closure
pour exécuter la fonction, il se lie à la valeur de la variable locale précédemment assignée closure
.
Et maintenant, chaque fois que vous appelez la closure_example
fonction, elle écrira la valeur incrémentée de la closure
variable car alert(closure)
est liée.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
Une "fermeture" est, en substance, un état local et du code, combinés dans un paquet. En général, l'état local provient d'une portée (lexicale) environnante et le code est (essentiellement) une fonction interne qui est ensuite renvoyée à l'extérieur. La fermeture est alors une combinaison des variables capturées vues par la fonction interne et du code de la fonction interne.
C'est une de ces choses qui est, malheureusement, un peu difficile à expliquer, à cause de son ignorance.
Une analogie que j'ai utilisée avec succès dans le passé était «imaginons que nous ayons quelque chose que nous appelons« le livre », dans la fermeture de la salle,« le livre »est cette copie là-bas, dans le coin, de TAOCP, mais sur la table de fermeture. , c’est cette copie d’un livre de Dresden Files. Ainsi, en fonction de la fermeture dans laquelle vous vous trouvez, le code "donnez-moi le livre" a pour résultat que différentes choses se passent. "
static
variable locale peut-elle être considérée comme une fermeture? Les fermetures à Haskell impliquent-elles un état?
static
variable locale, vous en avez exactement une).
Il est difficile de définir ce qu'est la clôture sans définir le concept d '«État».
Fondamentalement, dans un langage avec une 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
non seulement référence function foo()
mais aussi référence à l’état qui a foo
été laissé lors du dernier retour. La vraie magie se produit quand foo
d'autres fonctions sont définies plus en détail dans son champ d'application; c'est comme son propre mini-environnement (tout comme 'normalement' nous définissons des fonctions dans un environnement global).
Sur le plan fonctionnel, il peut résoudre bon nombre des mêmes problèmes que le mot clé 'statique' de C ++ (C?), Qui conserve l'état d'une variable locale tout au long de plusieurs appels de fonction; Cependant, cela ressemble plus à l'application du même principe (variable statique) à une fonction, car les fonctions sont des valeurs de première classe; fermeture ajoute la prise en charge de l’ensemble de l’état de la fonction à enregistrer (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 d'une 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 ++ dans une fonction (cela peut être faux à 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 compliqué, et cela varie probablement d’une langue à l’autre, mais il semble en Lua que chaque fois qu’une 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 myclosure
directement à la fonction / à l’état (au lieu de passer par la fonction anonyme pvalue
renvoyée ), ce qui reviendrait à 10; mais si nous accédons à l'état de myclosure par x (la fonction anonyme), vous pouvez voir qu'il pvalue
est bien vivant quelque part dans la mémoire. J'imagine qu'il y a un peu plus, mais quelqu'un peut peut-être mieux expliquer la nature de la mise en œuvre.
PS: Je ne connais pas une couche de C ++ 11 (autre que celles des versions précédentes), alors notez qu'il ne s'agit pas d'une comparaison entre les fermetures en C ++ 11 et Lua. En outre, toutes les "lignes" tracées de Lua à C ++ sont des similitudes en tant que variables statiques et fermetures ne sont pas identiques à 100%; même s’ils sont parfois utilisés pour résoudre des problèmes similaires.
Ce dont je ne suis pas sûr, c’est, dans l’exemple de code ci-dessus, si la fonction anonyme ou la fonction d’ordre supérieur est considérée comme la fermeture?
Une fermeture est une fonction à laquelle l'état associé:
En perl, vous créez des fermetures comme celle-ci:
#!/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 ++.
Il vous permet également de lier l’état actuel à l’objet:
#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");
}
Considérons une fonction simple:
function f1(x) {
// ... something
}
Cette fonction s'appelle une fonction de niveau supérieur car elle n'est imbriquée dans aucune autre fonction. Chaque fonction JavaScript s’associe à une liste d’objets appelée "chaîne d’étendue" . Cette chaîne d'étendue est une liste ordonnée d'objets. Chacun de ces objets définit des variables.
Dans les fonctions de niveau supérieur, la chaîne d'étendue est constituée d'un seul objet, l'objet global. Par exemple, la fonction f1
ci-dessus a une chaîne d'étendue qui contient un seul objet qui définit toutes les variables globales. (Notez que le terme "objet" ne signifie pas ici d'objet JavaScript, il s'agit simplement d'un objet défini par l'implémentation qui agit comme un conteneur de variables, dans lequel JavaScript peut "rechercher" des variables.)
Lorsque cette fonction est appelée, JavaScript crée un objet appelé "objet d'activation" et le place en haut de la chaîne d'étendue. Cet objet contient toutes les variables locales (par exemple x
ici). Nous avons donc maintenant deux objets dans la chaîne d’étendue: le premier est l’objet d’activation et, au-dessous, l’objet global.
Notez très attentivement que les deux objets sont placés dans la chaîne de l'oscilloscope à des moments différents. L'objet global est placé lorsque la fonction est définie (c'est-à-dire lorsque JavaScript a été analysé et que l'objet fonction a été créé) et l'objet d'activation est entré lorsque la fonction a été appelée.
Donc, nous savons maintenant ceci:
La situation devient intéressante lorsque nous traitons avec des fonctions imbriquées. Alors, créons-en un:
function f1(x) {
function f2(y) {
// ... something
}
}
Une fois f1
défini, nous obtenons une chaîne de portée contenant uniquement l'objet global.
Désormais, lorsqu’elle f1
est appelée, la chaîne d’étendue de f1
obtient l’objet d’activation. Cet objet d'activation contient la variable x
et la variable f2
qui est une fonction. Et, notez que cela f2
est en train de se définir. Par conséquent, à ce stade, JavaScript enregistre également une nouvelle chaîne d'étendue pour f2
. La chaîne d'étendue enregistrée pour cette fonction interne est la chaîne d'étendue actuelle en vigueur. La chaîne de champs d'application en vigueur est celle de f1
's. Par conséquent f2
« la chaîne de portée est f1
» de courant chaîne de portée - qui contient l'objet d'activation f1
et de l'objet global.
Quand f2
est appelé, il obtient son propre objet d'activation contenant y
, ajouté à sa chaîne d'étendue qui contient déjà l'objet d'activation de f1
et l'objet global.
Si une autre fonction imbriquée était définie au sein de celle-ci f2
, sa chaîne d'étendue contiendrait trois objets au moment de la définition (2 objets d'activation de deux fonctions externes et l'objet global) et 4 au moment de l'appel.
Nous comprenons donc maintenant comment fonctionne la chaîne d’étendue, mais nous n’avons pas encore parlé de fermeture.
La combinaison d'un objet de fonction et d'une 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 à l'aide de la même chaîne d'étendue que celle qui était en vigueur lors de la définition de la fonction, et peu importe qu'il y ait une fermeture. Les fermetures deviennent intéressantes quand elles sont appelées dans une chaîne de champs différente de celle qui était en vigueur lors de leur définition. Cela se produit le plus souvent lorsqu'un objet de fonction imbriqué est renvoyé à partir de la fonction dans laquelle il a été défini.
Lorsque la fonction revient, cet objet d'activation est supprimé de la chaîne d'étendue. S'il n'y a pas de fonctions imbriquées, il n'y a plus de référence à l'objet d'activation et il est récupéré. Si des fonctions imbriquées ont été définies, chacune de ces fonctions a une référence à la chaîne d'étendue et cette chaîne d'étendue fait référence à l'objet d'activation.
Si ces fonctions imbriquées restent dans leur fonction externe, elles seront elles-mêmes collectées, ainsi que l'objet d'activation auquel elles font référence. Mais si la fonction définit une fonction imbriquée et la renvoie ou la stocke dans une propriété quelque part, il y aura une référence externe à la fonction imbriquée. Il ne sera pas nettoyé et l'objet d'activation auquel il fait référence ne le sera pas non plus.
Dans notre exemple ci-dessus, nous ne retournons pas f2
depuis f1
. Par conséquent, lorsqu'un appel f1
retourne, son objet d'activation est supprimé de la chaîne d'étendue et les ordures collectées. Mais si nous avions quelque chose comme ça:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Ici, le retour f2
aura une chaîne d'étendue qui contiendra l'objet d'activation de f1
, et par conséquent, il ne sera pas récupéré. À ce stade, si nous appelons f2
, il sera en mesure d'accéder à f1
la variable, x
même si nous sommes en dehors de f1
.
Nous pouvons donc voir qu’une fonction garde sa chaîne d’étendue avec elle et qu’elle contient tous les objets d’activation des fonctions externes. C'est l'essence de la fermeture. Nous disons que les fonctions en JavaScript ont une "portée lexicale" , ce qui signifie qu'elles sauvegardent la portée qui était active quand elles ont été définies par opposition à la portée qui était active quand elles ont été appelées.
Il existe un certain nombre de techniques de programmation puissantes qui impliquent des fermetures telles que l'approximation de variables privées, la programmation événementielle, l'application partielle , etc.
Notez également que tout cela s'applique à toutes les langues prenant en charge les fermetures. Par exemple, PHP (5.3+), Python, Ruby, etc.
Une fermeture est une optimisation du compilateur (aka sucre syntaxique?). Certaines personnes ont également qualifié cet objet d'objet du pauvre .
Voir la réponse d'Eric Lippert : (extrait ci-dessous)
Le compilateur générera le code comme ceci:
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;
}
Avoir un sens?
En outre, vous avez demandé des comparaisons. VB et JScript créent tous deux des fermetures de la même manière.