Qu'est ce qu'un 'Fermeture'?

j'ai posé une question à propos de Currying et les fermetures ont été mentionnées. Qu'est ce qu'une fermeture? Comment est-il lié à lancer?

335
demandé sur Ben 2008-08-31 08:38:21

15 réponses

la portée des Variables

Lorsque vous déclarez une variable, cette variable a une portée. Généralement, les variables locales n'existent qu'à l'intérieur du bloc ou de la fonction dans laquelle vous déclarez.

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

si j'essaie d'accéder à une variable locale, la plupart des langues la chercheront dans la portée actuelle, puis dans les portées parentes jusqu'à ce qu'elles atteignent la portée racine.

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

quand un bloc ou une fonction est fait avec, ses variables locales ne sont plus nécessaires et sont généralement soufflé hors de la mémoire.

c'est ainsi que nous nous attendons normalement à ce que les choses fonctionnent.

Une fermeture est une persistance de la variable locale portée

une fermeture est une portée persistante qui s'accroche aux variables locales même après que l'exécution du code a quitté ce bloc. Les langages qui prennent en charge la fermeture (tels que JavaScript, Swift et Ruby) vous permettront de conserver une référence à un scope (y compris ses scopes parent), même après que le bloc dans lequel ces variables ont été déclarées a terminé l'exécution, à condition que vous gardiez une référence à ce bloc ou à cette fonction quelque part.

l'objet scope, et toutes ses variables locales, sont liés à la fonction, et persisteront aussi longtemps que cette fonction persistera.

cela nous donne la portabilité des fonctions. Nous pouvons nous attendre à ce que toutes les variables qui étaient dans la portée lorsque la fonction a été définie pour la première fois à encore être portée plus loin, lorsque nous appelons la fonction, même si nous appelons la fonction dans un contexte totalement différent.

par exemple

voici un exemple très simple en JavaScript qui illustre le point:

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

ici j'ai défini une fonction dans une fonction. La fonction interne accède à toutes les variables locales de la fonction externe, y compris a . La variable a entre dans le champ d'application de la intérieur de la fonction.

normalement quand une fonction sort,toutes ses variables locales sont emportées. Cependant , si nous retournons la fonction interne et l'assignons à une variable fnc , de sorte qu'elle persiste après outer a quitté, toutes les variables qui étaient dans la portée lorsque inner a été défini persistent aussi . La variable a a été fermée -- elle est à l'intérieur d'une fermeture.

noter que la variable a est totalement privé à fnc . C'est une façon de créer des variables privées dans un langage de programmation fonctionnel tel que JavaScript.

comme vous pourriez être en mesure de deviner, quand j'appelle fnc() il imprime la valeur de a , qui est"1".

dans une langue sans fermeture, la variable a aurait été ramassée et Jetée lorsque la fonction outer aurait cessé. Appeler la fnc aurait jeté une erreur parce que a n'existe plus.

en JavaScript, la variable a persiste parce que la variable scope est créée lorsque la fonction est déclarée pour la première fois, et persiste aussi longtemps que la fonction continue d'exister.

a fait partie du champ d'application de outer . Le champ d'application de inner a un pointeur parent vers le champ d'application de outer . fnc est une variable qui pointe vers inner . a persiste aussi longtemps que fnc persiste. a est dans la fermeture.

525
répondu superluminary 2016-11-24 11:44:17

je vais donner un exemple (en JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

ce que fait cette fonction, makeCounter, c'est qu'elle renvoie une fonction, que nous avons appelée x, qui va compter d'un à chaque fois qu'elle est appelée. Puisqu'on ne fournit aucun paramètre à x, il doit se souvenir du compte. Il sait où le trouver en fonction de ce qu'on appelle la portée lexicale - il doit regarder à l'endroit où il est défini pour trouver la valeur. Cette valeur "cachée" est ce qu'on appelle une fermeture.

voici à nouveau mon exemple actuel:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

ce que vous pouvez voir est que lorsque vous appelez ajouter avec le paramètre a (qui est 3), cette valeur est contenue dans la fermeture de la fonction retournée que nous définissons pour être add3. De cette façon, quand nous appelons add3, il sait où trouver la valeur d'effectuer l'addition.

80
répondu Kyle Cronin 2016-01-30 02:20:17

la réponse de Kyle est assez bonne. Je pense que la seule clarification supplémentaire est que la fermeture est fondamentalement un instantané de la pile au moment ou la fonction lambda est créé. Ensuite, lorsque la fonction est ré-exécutée, la pile est restaurée dans cet état avant d'exécuter la fonction. Ainsi comme Kyle le mentionne, cette valeur cachée ( count ) est disponible lorsque la fonction lambda s'exécute.

54
répondu Ben Childs 2017-05-23 12:26:36

une fonction de fermeture est une fonction qui peut servir d'état de référence dans une autre fonction. Par exemple, en Python, cela utilise la fermeture "inner":

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
26
répondu John Millikin 2008-08-31 04:54:04

pour faciliter la compréhension des fermetures, il pourrait être utile d'examiner comment elles pourraient être mises en œuvre dans un langage procédural. Cette explication suivra une mise en œuvre simpliste des fermetures dans le schéma.

pour commencer, je dois introduire le concept d'espace de noms. Lorsque vous saisissez une commande dans un interpréteur de schéma, il doit évaluer les différents symboles de l'expression et obtenir leur valeur. Exemple:

(define x 3)

(define y 4)

(+ x y) returns 7

le définissez des expressions stockez la valeur 3 dans le spot pour x et la valeur 4 dans le spot pour Y. Ensuite, lorsque nous appelons (+x y), l'interpréteur regarde les valeurs dans l'espace de noms et est capable d'effectuer l'opération et de retourner 7.

cependant, dans Scheme il y a des expressions qui vous permettent d'outrepasser temporairement la valeur d'un symbole. Voici un exemple:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

ce que le mot-clé let fait est d'introduire un nouvel espace de noms avec x comme valeur 5. Vous remarquera qu'il est toujours capable de voir que y est 4, ce qui rend la somme retournée à 9. Vous pouvez aussi voir qu'une fois l'expression terminée, x est de nouveau 3. En ce sens, x a été temporairement masquée par la valeur locale.

les langages procéduraux et orientés objet ont un concept similaire. Chaque fois que vous déclarez une variable dans une fonction qui a le même nom qu'une variable globale, vous obtenez le même effet.

comment mettre en œuvre ceci? Simple façon est avec une liste chaînée - tête contient la nouvelle valeur et la queue contient l'ancien espace de noms. Lorsque vous avez besoin de rechercher un symbole, vous commencez par la tête et de travailler votre chemin vers le bas de la queue.

passons maintenant à l'implémentation de fonctions de première classe pour le moment. Plus ou moins, une fonction est un ensemble d'instructions à exécuter lorsque la fonction est appelée culminant dans la valeur de retour. Quand nous lisons dans une fonction, nous pouvons conserver ces instructions derrière les coulisses et lancez-les quand la fonction est appelée.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

nous Nous définissons x 3 et plus-x à son paramètre, y, plus la valeur de x. Enfin, nous appelons plus-x dans un environnement où x a été masqué par un nouveau x, celui-ci 5 évalué. Si nous stockons simplement l'opération, (+x y), pour la fonction plus-x, puisque nous sommes dans le contexte de x étant 5 le résultat retourné serait 9. C'est ce qu'on appelle le cadrage dynamique.

Toutefois, le Schéma, le Common Lisp, et beaucoup d'autres langues ont ce qu'on appelle la portée lexicale - en plus de stocker l'opération (+ x y) nous stockons également l'espace de noms à ce point particulier. De cette façon, lorsque nous regardons les valeurs, nous pouvons voir que x, dans ce contexte, est vraiment 3. C'est une clôture.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

en résumé, nous pouvons utiliser une liste liée pour stocker l'état de l'espace de noms au moment de la définition de la fonction, nous permettant d'accéder aux variables à partir des scopes d'enclosage, ainsi que nous fournir la capacité d'localement masque d'une variable sans affecter le reste du programme.

23
répondu Kyle Cronin 2011-01-23 00:21:29

tout D'abord, contrairement à ce que la plupart des gens ici vous disent, fermeture est pas une fonction ! Alors qu'est-ce que est ?

C'est un ensemble de symboles définis dans le "contexte d'environnement" d'une fonction (connu sous le nom de environnement ) qui en font une expression fermée (c'est-à-dire une expression dans laquelle chaque symbole est défini et a une valeur, donc il peut être évalué).

par exemple, lorsque vous avez une fonction JavaScript:

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

c'est une expression fermée parce que tous les symboles qui s'y trouvent y sont définis (leur signification est claire), donc vous pouvez l'évaluer. En d'autres termes, il s'agit de autonome .

mais si vous avez une fonction comme celle-ci:

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

c'est un ouvrir expression car il y a des symboles qui n'a pas été défini. À savoir, y . En regardant cette fonction, nous ne pouvons pas dire ce qu'est y et ce que cela signifie, Nous ne savons pas sa valeur, donc nous ne pouvons pas évaluer cette expression. I. e. nous ne pouvons pas appeler cette fonction jusqu'à nous dire ce que y est censé signifier. Ce y est appelé une variable libre .

This y demande une définition, mais cette définition ne fait pas partie de la fonction – elle est définie ailleurs, dans son" contexte environnant "(aussi connu sous le nom de environnement ). C'est du moins ce que nous espérons: p

par exemple, il pourrait être défini globalement:

var y = 7;

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

Ou il pourrait être défini dans une fonction qui l'enveloppe:

var global = 2;

function wrapper(y) {
   var w = "unused";

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

}

La partie de l'environnement qui donne la les variables libres dans une expression leurs significations, est le fermeture . Il est appelé de cette façon, parce qu'il transforme une expression ouverte en une expression fermée , en fournissant ces définitions manquantes pour toutes ses variables libres , afin que nous puissions l'évaluer.

dans l'exemple ci-dessus, la fonction interne (dont nous n'avons pas donné de nom parce que nous n'en avions pas besoin) est expression ouverte parce que la variable y dans elle est libre – sa définition est en dehors de la fonction, dans la fonction qui l'enveloppe. Le environnement pour cette fonction anonyme est l'ensemble des variables:

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

maintenant, la fermeture est la partie de cet environnement qui ferme la fonction interne en fournissant la définitions pour l'ensemble de ses variables libres . Dans notre cas, la seule variable libre dans la fonction interne était y , donc la fermeture de cette fonction est ce sous-ensemble de son environnement:

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

les deux autres symboles définis dans l'environnement sont et non faisant partie de la fermeture de cette fonction, parce qu'elle ne les oblige pas à courir. Ils ne sont pas nécessaires pour fermer it.

Plus sur la théorie derrière cela ici: https://stackoverflow.com/a/36878651/434562

il est intéressant de noter que dans l'exemple ci-dessus, la fonction d'enrubannage renvoie sa fonction interne en tant que valeur. Le moment que nous appelons cette fonction peut être éloigné dans le temps à partir du moment où la fonction a été définie (ou créée). En particulier, sa fonction d'enrubannage ne fonctionne plus, et ses paramètres qui ont été sur la pile d'appels ne sont plus là :P Cela fait un problème, parce que la fonction interne a besoin y pour être là quand il est appelé! En d'autres termes, il nécessite les variables de sa fermeture à survivre la fonction de wrapper et être là Quand nécessaire. Par conséquent, la fonction interne doit faire un snapshot de ces variables qui font sa fermeture et les stocker dans un endroit sûr pour une utilisation ultérieure. (Quelque part à l'extérieur de la pile d'appel.)

et c'est pourquoi les gens confondent souvent le terme fermeture pour être ce type spécial de fonction qui peut faire de tels instantanés des variables externes qu'ils utilisent, ou la structure de données utilisée pour stocker ces variables pour plus tard. Mais j'espère que vous comprenez maintenant qu'ils sont pas la fermeture elle – même-ils sont juste des moyens de mettre en œuvre fermetures dans un langage de programmation, ou des mécanismes de langage qui permet aux variables de la fermeture de la fonction d'être là au besoin. Il y a beaucoup d'idées fausses autour des fermetures qui (inutilement) rendent ce sujet beaucoup plus confus et compliqué qu'il ne l'est réellement.

22
répondu SasQ 2017-05-23 11:55:03

voici un exemple réel de pourquoi les fermetures déchirent... C'est tout droit sorti de mon code Javascript. Permettez-moi d'illustrer.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

et voici comment vous l'utiliseriez:

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

maintenant, imaginez que vous voulez que la lecture commence en retard, comme par exemple 5 secondes plus tard après que ce code fonctionne. C'est facile avec delay et c'est la fin:

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

quand vous appelez delay avec 5000 ms, le premier extrait s'exécute, et les magasins de l'passées en arguments à sa fermeture. Puis 5 secondes plus tard, lorsque le rappel setTimeout se produit, la fermeture maintient toujours ces variables, de sorte qu'elle peut appeler la fonction d'origine avec les paramètres d'origine.

Il s'agit d'un type de currying, ou décoration de fonction.

sans fermetures, vous auriez à maintenir d'une manière ou d'une autre ces variables état en dehors de la fonction, donc salissant code en dehors la fonction avec quelque chose qui appartient logiquement à l'intérieur. L'utilisation de fermetures peut grandement améliorer la qualité et la lisibilité de votre code.

10
répondu adamJLev 2010-11-07 19:04:23
Les fonctions

ne contenant Aucune variable libre sont appelées fonctions pures.

Les fonctions

contenant une ou plusieurs variables libres sont appelées fermetures.

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-without-free-variables-are-pure-are-closures-impure

5
répondu soundyogi 2016-02-07 13:38:24

dans une situation normale, les variables sont liées par la règle de détermination de la portée: les variables locales ne fonctionnent qu'à l'intérieur de la fonction définie. La fermeture est un moyen de briser temporairement cette règle pour des raisons de commodité.

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

dans le code ci-dessus, lambda(|n| a_thing * n} est la fermeture parce que a_thing est mentionné par la lambda (un créateur de fonction anonyme).

maintenant, si vous mettez la fonction anonyme résultante dans une variable de fonction.

foo = n_times(4)

foo va briser la règle normale de détermination de la portée et commencer à utiliser 4 à l'interne.

foo.call(3)

renvoie 12.

4
répondu Eugene Yokota 2008-08-31 05:31:57

en bref, le pointeur de fonction est juste un pointeur à un endroit dans la base de code de programme (comme le compteur de programme). Alors que fermeture = pointeur de fonction + cadre de pile .

.

2
répondu RoboAlex 2013-09-07 13:50:19

tl; dr

une fonction de fermeture est une fonction et sa portée est assignée à (ou utilisée comme) une variable. Ainsi, la fermeture du nom: la portée et la fonction sont fermées et utilisées comme n'importe quelle autre entité.

en profondeur explication de style Wikipédia

selon Wikipedia, une fermeture est:

Techniques pour la mise en œuvre de lexicalement étendue de liaison de nom dans langues avec des fonctions de première classe.

ça veut dire Quoi? Examinons quelques définitions.

j'expliquerai les fermetures et autres définitions connexes en utilisant cet exemple:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

fonctions de première classe

cela signifie essentiellement nous pouvons utiliser des fonctions comme n'importe quelle autre entité . Nous pouvons les modifier, de les transmettre comme arguments, retournez - les à partir des fonctions ou assignez-les pour les variables. Techniquement parlant, ils sont citoyens de première classe , d'où le nom: fonctions de première classe.

dans l'exemple ci-dessus, startAt renvoie une fonction ( anonyme ) qui est affectée à closure1 et closure2 . Donc, comme vous le voyez, JavaScript traite les fonctions comme n'importe quelle autre entité (citoyens de première classe).

Nom de liaison

Name binding consiste à trouver quelles données une variable (identificateur) références . La portée est vraiment importante ici, car c'est ce qui déterminera la façon dont une obligation sera résolue.

dans l'exemple ci-dessus:

  • dans la portée de la fonction d'anonymat interne, y est lié à 3 .
  • dans le champ d'application de startAt , x est lié à 1 ou 5 (selon la fermeture).

dans la portée de la fonction anonyme, x n'est lié à aucune valeur, il doit donc être résolu dans une portée supérieure ( startAt s).

portée Lexicale

comme Wikipedia dit , la portée:

Est la région d'un programme d'ordinateur dans lequel la liaison est valide: où le nom peut être utilisé pour se référer à l'entité .

il y a deux techniques:

  • cadrage Lexical (statique): la définition d'une variable est résolue en recherchant son bloc contenant ou sa fonction, puis si cela échoue la recherche du bloc contenant extérieur, et ainsi de suite.
  • Dynamique de la portée: l'Appel d' la fonction est recherché, alors la fonction qui a appelé cette fonction d'appel, et ainsi de suite, en progressant jusqu'à la pile d'appel.

Pour plus d'explication, découvrez cette question et prendre un coup d'oeil à Wikipedia .

dans l'exemple ci-dessus, nous pouvons voir que JavaScript est lexicalement scopé, parce que lorsque x est résolu, la liaison est recherchée dans le cadre supérieur ( startAt 's) , basé sur le code source (la fonction anonyme qui cherche x est définie à l'intérieur de startAt ) et non basé sur la pile d'appels, la façon (la portée où) la fonction a été appelée.

l'Habillage (closuring)

dans notre exemple, quand nous appelons startAt , il retournera une fonction (de première classe) qui sera assignée à closure1 et closure2 ainsi une fermeture est créée, parce que les variables passées 1 et 5 seront être sauvegardé dans le champ d'application de startAt , qui sera inclus avec la fonction anonyme retournée. Lorsque nous appelons cette fonction anonyme via closure1 et closure2 avec le même argument ( 3 ), la valeur de y sera trouvée immédiatement (comme c'est le paramètre de cette fonction), mais x n'est pas liée dans la portée de la fonction anonyme, de sorte que la résolution continue dans la portée (lexique) de la fonction supérieure (qui a été enregistrée dans la fermeture) où x est lié à 1 ou 5 . Maintenant nous savons tout pour la sommation de sorte que le résultat peut être retourné, puis imprimé.

maintenant, vous devez comprendre les fermetures et comment elles se comportent, ce qui est une partie fondamentale de JavaScript.

Nourrissage

Oh, et vous avez également appris que nourrissage : vous utilisez les fonctions (fermetures) pour passer chaque argument d'une opération au lieu d'utiliser une fonction avec plusieurs paramètres.

2
répondu totymedli 2017-07-04 23:43:59

voici un autre exemple de la vie réelle, et en utilisant un langage de script populaire dans les jeux - Lua. J'ai eu besoin de changer légèrement la façon dont une fonction de bibliothèque fonctionnait pour éviter un problème avec stdin n'étant pas disponible.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

la valeur de old_dofile disparaît lorsque ce bloc de code termine son champ d'application (parce qu'il est local), cependant la valeur a été enfermée dans une fermeture, de sorte que la nouvelle fonction dofile redéfinie peut y accéder, ou plutôt une copie stockée avec de la fonction comme une 'amont'.

0
répondu Nigel Atkinson 2011-05-12 05:57:49

De Lua.org :

Lorsqu'une fonction est écrite enfermée dans une autre fonction, elle a un accès complet aux variables locales à partir de la fonction d'enclos; cette fonctionnalité est appelée cadrage lexical. Même si cela peut paraître évident, il n'est pas. Lexical scoping, plus fonctions de première classe, est un concept puissant dans un langage de programmation, mais peu de langues soutiennent ce concept.

0
répondu user5731811 2015-12-30 23:06:50

Si vous êtes dans le monde Java, vous pouvez comparer une fermeture avec une fonction membre d'une classe. Regardez cet exemple

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

la fonction g est une fermeture: g ferme a in. Ainsi g peut être comparé avec une fonction membre, a peut être comparé avec un champ de classe, et la fonction f avec une classe.

0
répondu ericj 2016-04-02 19:35:10

fermetures Chaque fois que nous avons une fonction définie à l'intérieur d'une autre fonction, la fonction interne a accès aux variables déclarées dans la fonction externe. Les fermetures sont mieux expliquées avec des exemples. Dans la liste 2-18, vous pouvez voir que la fonction interne a accès à une variable (variableinoterfunction) de la extérieur de la portée. Les variables dans la fonction externe ont été fermées par (ou lié à) la fonction interne. D'où le terme fermeture. Le concept en lui-même est assez simple et assez intuitif.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

source: http://index-of.es/Varios/Basarat%20Ali%20Syed%20 (auth.) -Début%20Node.js-Apress%20 (2014).pdf

0
répondu shohan 2017-08-03 09:15:35