Quelle est la différence entre une «fermeture» et une «lambda»?


811

Quelqu'un pourrait-il expliquer? Je comprends les concepts de base derrière eux, mais je les vois souvent utilisés de manière interchangeable et je suis confus.

Et maintenant que nous sommes ici, en quoi diffèrent-ils d'une fonction régulière?


85
Les lambdas sont une construction de langage (fonctions anonymes), les fermetures sont une technique d'implémentation pour implémenter des fonctions de première classe (anonymes ou non). Malheureusement, cela est souvent confondu par de nombreuses personnes.
Andreas Rossberg



Pour les fermetures PHP, voir php.net/manual/en/class.closure.php . Ce n'est pas ce à quoi un programmeur JavaScript pourrait s'attendre.
PaulH

La réponse de SasQ est excellente. À mon humble avis, cette question serait plus utile aux utilisateurs des OS si elle guidait les téléspectateurs vers cette réponse.
AmigoNico

Réponses:


704

Un lambda est juste une fonction anonyme - une fonction définie sans nom. Dans certains langages, tels que Scheme, ils sont équivalents aux fonctions nommées. En fait, la définition de la fonction est réécrite comme liant un lambda à une variable en interne. Dans d'autres langages, comme Python, il existe des distinctions (plutôt inutiles) entre eux, mais ils se comportent de la même manière sinon.

Une fermeture est une fonction qui se referme sur l' environnement dans lequel elle a été définie. Cela signifie qu'il peut accéder aux variables qui ne figurent pas dans sa liste de paramètres. Exemples:

def func(): return h
def anotherfunc(h):
   return func()

Cela provoquera une erreur, car funcne se ferme pas sur l'environnement dans anotherfunc- hn'est pas défini. funcne ferme que sur l'environnement mondial. Cela fonctionnera:

def anotherfunc(h):
    def func(): return h
    return func()

Parce qu'ici, funcest défini dans anotherfuncet en python 2.3 et supérieur (ou un certain nombre comme celui-ci) quand ils ont presque obtenu des fermetures correctes (la mutation ne fonctionne toujours pas), cela signifie qu'il se ferme sur anotherfunc l'environnement de et peut accéder aux variables à l'intérieur de il. Dans Python 3.1+, la mutation fonctionne également lors de l'utilisation du nonlocalmot clé .

Un autre point important - funccontinuera de fermer anotherfuncl'environnement de même s'il n'est plus évalué anotherfunc. Ce code fonctionnera également:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

Cela imprimera 10.

Comme vous le constatez, cela n'a rien à voir avec les lambda - ce sont deux concepts différents (bien que liés).


1
Claudiu, à ma connaissance incertaine, le python n'a jamais tout à fait fermé correctement. Ont-ils résolu le problème de mutabilité alors que je ne regardais pas? Tout à fait possible ...
Simon

Simon - vous avez raison, ils ne sont toujours pas entièrement corrects. je pense que python 3.0 ajoutera un hack pour contourner ce problème. (quelque chose comme un identifiant «non local»). je vais changer mon ansewr pour refléter cela.
Claudiu

2
@AlexanderOrlov: Ce sont à la fois des lambdas et des fermetures. Java avait des fermetures auparavant via des classes internes anonymes. Maintenant, cette fonctionnalité a été rendue syntaxiquement plus facile via les expressions lambda. Donc, probablement l'aspect le plus pertinent de la nouvelle fonctionnalité est qu'il existe maintenant des lambdas. Ce n'est pas faux de les appeler lambdas, ce sont en effet des lambdas. Je ne sais pas pourquoi les auteurs de Java 8 peuvent choisir de ne pas souligner le fait qu'il s'agit de fermetures.
Claudiu

2
@AlexanderOrlov parce que les lambdas Java 8 ne sont pas de véritables fermetures, ce sont des simulations de fermetures. Ils sont plus similaires aux fermetures Python 2.3 (pas de mutabilité, d'où l'exigence que les variables référencées soient `` effectivement définitives ''), et compilent en interne des fonctions de non-fermeture qui prennent toutes les variables référencées dans la portée englobante comme paramètres cachés.
Ramassage Logan

7
@Claudiu Je pense que la référence à une implémentation de langage particulière (Python) peut compliquer la réponse de manière excessive. La question est complètement indépendante de la langue (ainsi qu'elle n'a pas de balises spécifiques à la langue).
Matthew

319

Il y a beaucoup de confusion autour des lambdas et des fermetures, même dans les réponses à cette question StackOverflow ici. Au lieu de demander à des programmeurs aléatoires qui ont appris les fermetures de s'entraîner avec certains langages de programmation ou d'autres programmeurs ignorants, faites un voyage vers la source (où tout a commencé). Et puisque les lambdas et les fermetures proviennent du Lambda Calculus inventé par Alonzo Church dans les années 30 avant même que les premiers ordinateurs électroniques n'existent, c'est la source dont je parle.

Lambda Calculus est le langage de programmation le plus simple au monde. Les seules choses que vous pouvez y faire: ►

  • APPLICATION: Application d'une expression à une autre, notée f x.
    (Considérez-le comme un appel de fonction , où fest la fonction et xson seul paramètre)
  • ABSTRACTION: lie un symbole apparaissant dans une expression pour indiquer que ce symbole n'est qu'un "emplacement", une boîte vide en attente d'être remplie de valeur, une "variable" pour ainsi dire. Cela se fait en ajoutant une lettre grecque λ(lambda), puis le nom symbolique (par exemple x), puis un point .avant l'expression. Cela convertit ensuite l'expression en une fonction qui attend un paramètre .
    Par exemple: λx.x+2prend l'expression x+2et indique que le symbole xdans cette expression est une variable liée - il peut être remplacé par une valeur que vous fournissez comme paramètre.
    Notez que la fonction définie de cette façon est anonyme- il n'a pas de nom, de sorte que vous ne pouvez pas s'y référer encore, mais vous pouvez appeler immédiatement il (rappelez - vous l' application?) En lui fournissant le paramètre qu'il attend, comme ceci: (λx.x+2) 7. Ensuite, l'expression (dans ce cas, une valeur littérale) 7est substituée comme xdans la sous-expression x+2du lambda appliqué, donc vous obtenez 7+2, qui se réduit ensuite à 9des règles arithmétiques communes.

Nous avons donc résolu l'un des mystères:
lambda est la fonction anonyme de l'exemple ci-dessus λx.x+2,.


Dans différents langages de programmation, la syntaxe de l'abstraction fonctionnelle (lambda) peut différer. Par exemple, en JavaScript, cela ressemble à ceci:

function(x) { return x+2; }

et vous pouvez l'appliquer immédiatement à un paramètre comme celui-ci:

(function(x) { return x+2; })(7)

ou vous pouvez stocker cette fonction anonyme (lambda) dans une variable:

var f = function(x) { return x+2; }

qui lui donne effectivement un nom f, vous permettant de vous y référer et de l'appeler plusieurs fois plus tard, par exemple:

alert(  f(7) + f(10)  );   // should print 21 in the message box

Mais vous n'aviez pas à le nommer. Vous pourriez l'appeler immédiatement:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

Dans LISP, les lambdas sont faites comme ceci:

(lambda (x) (+ x 2))

et vous pouvez appeler un tel lambda en l'appliquant immédiatement à un paramètre:

(  (lambda (x) (+ x 2))  7  )


OK, il est maintenant temps de résoudre l'autre mystère: qu'est-ce qu'une fermeture . Pour ce faire, parlons des symboles ( variables ) dans les expressions lambda.

Comme je l'ai dit, ce que l'abstraction lambda fait est de lier un symbole dans sa sous-expression, afin qu'il devienne un paramètre substituable . Un tel symbole est appelé lié . Mais que se passe-t-il s'il y a d'autres symboles dans l'expression? Par exemple: λx.x/y+2. Dans cette expression, le symbole xest lié par l'abstraction lambda qui le λx.précède. Mais l'autre symbole,, yn'est pas lié - il est gratuit . Nous ne savons pas ce que c'est et d'où il vient, donc nous ne savons pas ce que cela signifie et quelle valeur il représente, et donc nous ne pouvons pas évaluer cette expression avant d'avoir compris ce que ysignifie.

En fait, il en va de même pour les deux autres symboles, 2et +. C'est juste que nous sommes si familiers avec ces deux symboles que nous oublions généralement que l'ordinateur ne les connaît pas et nous devons lui dire ce qu'ils signifient en les définissant quelque part, par exemple dans une bibliothèque ou dans la langue elle-même.

Vous pouvez penser aux symboles libres tels qu'ils sont définis ailleurs, en dehors de l'expression, dans son "contexte environnant", qui est appelé son environnement . L'environnement pourrait être une expression plus grande dont cette expression fait partie (comme l'a dit Qui-Gon Jinn: "Il y a toujours un plus gros poisson";)), ou dans une bibliothèque, ou dans le langage lui-même (en tant que primitif ).

Cela nous permet de diviser les expressions lambda en deux catégories:

  • Expressions CLOSED: chaque symbole qui apparaît dans ces expressions est lié par une abstraction lambda. En d'autres termes, ils sont autonomes ; ils ne nécessitent aucun contexte environnant pour être évalués. Ils sont également appelés combinateurs .
  • Expressions OUVERTES: certains symboles dans ces expressions ne sont pas liés - c'est-à-dire que certains des symboles qui s'y trouvent sont libres et nécessitent des informations externes, et ils ne peuvent donc pas être évalués tant que vous n'avez pas fourni les définitions de ces symboles.

Vous pouvez FERMER une expression lambda ouverte en fournissant l' environnement , qui définit tous ces symboles libres en les liant à certaines valeurs (qui peuvent être des nombres, des chaînes, des fonctions anonymes alias lambdas, peu importe…).

Et voici la partie de fermeture :
La fermeture d'une expression lambda est cet ensemble particulier de symboles définis dans le contexte extérieur (environnement) qui donnent des valeurs aux symboles libres dans cette expression, les rendant ainsi non libres. Il transforme une expression lambda ouverte , qui contient encore quelques symboles libres "non définis", en une expression fermée , qui n'a plus de symboles libres.

Par exemple, si vous avez l'expression lambda suivante:, λx.x/y+2le symbole xest lié, tandis que le symbole yest libre, par conséquent l'expression est openet ne peut pas être évaluée à moins que vous ne disiez ce que ysignifie (et la même chose avec +et 2, qui sont également libres). Mais supposons que vous ayez également un environnement comme celui-ci:

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

Cet environnement fournit des définitions pour tous les symboles (gratuit) « non défini » de notre expression lambda ( y, +, 2), et plusieurs symboles supplémentaires ( q, w). Les symboles que nous devons définir sont ce sous-ensemble de l'environnement:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

et c'est précisément la fermeture de notre expression lambda:>

En d'autres termes, il ferme une expression lambda ouverte. C'est de là que vient la fermeture du nom , et c'est pourquoi tant de réponses de personnes dans ce fil ne sont pas tout à fait correctes: P


Alors pourquoi se trompent-ils? Pourquoi tant d'entre eux disent-ils que les fermetures sont des structures de données en mémoire, ou certaines caractéristiques des langages qu'ils utilisent, ou pourquoi confondent-elles les fermetures avec les lambdas? : P

Eh bien, les marketoids d'entreprise de Sun / Oracle, Microsoft, Google etc. sont à blâmer, parce que c'est ce qu'ils ont appelé ces constructions dans leurs langages (Java, C #, Go, etc.). Ils appellent souvent des «fermetures» ce qui est censé être simplement des lambdas. Ou ils appellent les «fermetures» une technique particulière qu'ils ont utilisée pour implémenter la portée lexicale, c'est-à-dire le fait qu'une fonction peut accéder aux variables qui ont été définies dans sa portée externe au moment de sa définition. Ils disent souvent que la fonction "renferme" ces variables, c'est-à-dire les capture dans une certaine structure de données pour éviter qu'elles ne soient détruites une fois la fonction externe terminée. Mais ce ne sont que des «étymologies du folklore» et du marketing post factum inventés , ce qui ne fait que rendre les choses plus confuses,

Et c'est encore pire en raison du fait qu'il y a toujours un peu de vérité dans ce qu'ils disent, ce qui ne vous permet pas de le rejeter facilement comme faux: P Permettez-moi d'expliquer:

Si vous souhaitez implémenter un langage qui utilise des lambdas en tant que citoyens de première classe, vous devez leur permettre d'utiliser des symboles définis dans leur contexte environnant (c'est-à-dire utiliser des variables libres dans vos lambdas). Et ces symboles doivent être là même lorsque la fonction environnante revient. Le problème est que ces symboles sont liés à un stockage local de la fonction (généralement sur la pile des appels), qui ne sera plus là lorsque la fonction reviendra. Par conséquent, pour qu'un lambda fonctionne comme vous l'attendez, vous devez en quelque sorte «capturer» toutes ces variables libres de son contexte externe et les enregistrer pour plus tard, même lorsque le contexte externe aura disparu. Autrement dit, vous devez trouver la fermeturede votre lambda (toutes ces variables externes qu'il utilise) et stockez-la ailleurs (soit en faisant une copie, soit en préparant de l'espace pour elles en amont, ailleurs que sur la pile). La méthode que vous utilisez pour atteindre cet objectif est un "détail d'implémentation" de votre langue. Ce qui est important ici, c'est la fermeture , qui est l'ensemble des variables libres de l' environnement de votre lambda qui doivent être enregistrées quelque part.

Il n'a pas fallu trop de temps aux gens pour commencer à appeler la structure de données réelle qu'ils utilisent dans les implémentations de leur langage pour implémenter la fermeture comme la «fermeture» elle-même. La structure ressemble généralement à ceci:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

et ces structures de données sont transmises en tant que paramètres à d'autres fonctions, renvoyées par les fonctions et stockées dans des variables, pour représenter les lambdas, et leur permettant d'accéder à leur environnement englobant ainsi qu'au code machine à exécuter dans ce contexte. Mais ce n'est qu'un moyen (parmi d'autres) de mettre en œuvre la fermeture, pas la fermeture elle-même.

Comme je l'ai expliqué ci-dessus, la fermeture d'une expression lambda est le sous-ensemble de définitions de son environnement qui donnent des valeurs aux variables libres contenues dans cette expression lambda, fermant effectivement l'expression (transformer une expression lambda ouverte , qui ne peut pas encore être évaluée, en une expression lambda fermée , qui peut ensuite être évaluée, puisque tous les symboles qu'elle contient sont maintenant définis).

Tout le reste n'est qu'un "culte du fret" et une "magie voo-doo" de programmeurs et de vendeurs de langage ignorant les véritables racines de ces notions.

J'espère que cela répond à vos questions. Mais si vous aviez des questions complémentaires, n'hésitez pas à les poser dans les commentaires, et je vais essayer de mieux l'expliquer.


50
Meilleure réponse expliquant les choses de manière générique plutôt que spécifique à la langue
Shishir Arora

39
j'aime ce genre d'approche pour expliquer les choses. En commençant par le tout début, en expliquant comment les choses fonctionnent, puis comment les idées fausses actuelles ont été créées. Cette réponse doit aller en haut.
Sharky

1
@SasQ> la fermeture d'une expression lambda est le sous-ensemble de définitions dans son environnement qui donne des valeurs aux variables libres contenues dans cette expression lambda <Donc dans votre exemple de structure de données, cela signifie-t-il qu'il est plus approprié de dire le "pointeur vers le environnement de la fonction lambda "est la fermeture?
Jeff M

3
Bien que le calcul Lambda ressemble à un langage machine pour moi, je dois admettre qu'il s'agit d'un langage "trouvé" en contraste avec un langage "créé". Et donc beaucoup moins soumis à des conventions arbitraires, et beaucoup plus adapté à saisir la structure sous-jacente de la réalité. Nous pouvons trouver des détails dans Linq, JavaScript, F # plus accessibles / accessibles, mais le calcul Lambda va au cœur du sujet sans distraction.
StevePoling

1
J'apprécie que vous ayez réitéré votre argument à plusieurs reprises, avec un libellé légèrement différent à chaque fois. Cela contribue à renforcer le concept. Je souhaite que plus de gens fassent cela.
johnklawlor

175

Quand la plupart des gens pensent aux fonctions , ils pensent aux fonctions nommées :

function foo() { return "This string is returned from the 'foo' function"; }

Ceux-ci sont appelés par leur nom, bien sûr:

foo(); //returns the string above

Avec les expressions lambda , vous pouvez avoir des fonctions anonymes :

 @foo = lambda() {return "This is returned from a function without a name";}

Avec l'exemple ci-dessus, vous pouvez appeler le lambda via la variable à laquelle il a été affecté:

foo();

Cependant, il est plus utile que d'attribuer des fonctions anonymes à des variables de les passer de ou vers des fonctions d'ordre supérieur, c'est-à-dire des fonctions qui acceptent / renvoient d'autres fonctions. Dans beaucoup de ces cas, il n'est pas nécessaire de nommer une fonction:

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

Une fermeture peut être une fonction nommée ou anonyme, mais elle est connue en tant que telle lorsqu'elle "ferme" des variables dans la portée où la fonction est définie, c'est-à-dire que la fermeture fera toujours référence à l'environnement avec toutes les variables externes utilisées dans le fermeture elle-même. Voici une fermeture nommée:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

Cela ne semble pas beaucoup, mais que se passe-t-il si tout cela était dans une autre fonction et que vous passiez incrementXà une fonction externe?

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

C'est ainsi que vous obtenez des objets avec état dans la programmation fonctionnelle. Puisque nommer "incrementX" n'est pas nécessaire, vous pouvez utiliser un lambda dans ce cas:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }

14
quelle langue utilisez-vous ici?
Claudiu

7
C'est essentiellement un pseudocode. Il y a du lisp et du JavaScript, ainsi qu'un langage que je conçois appelé "@" ("at"), nommé d'après l'opérateur de déclaration de variable.
Mark Cidade

3
@MarkCidade, alors où est cette langue @? Existe-t-il une documentation et un téléchargement?
Pacerier

5
Pourquoi ne pas prendre Javascript et ajouter une contrainte de déclaration de variables avec le signe @ en tête? Cela gagnerait un peu de temps :)
Nemoden

5
@Pacerier: J'ai commencé à implémenter le langage: github.com/marxidad/At2015
Mark Cidade

54

Toutes les fermetures ne sont pas des lambdas et toutes les lambdas ne sont pas des fermetures. Les deux sont des fonctions, mais pas nécessairement de la manière dont nous sommes habitués à le savoir.

Un lambda est essentiellement une fonction définie en ligne plutôt que la méthode standard de déclaration de fonctions. Les lambdas peuvent souvent être transmis comme des objets.

Une fermeture est une fonction qui enferme son état environnant en référençant des champs externes à son corps. L'état fermé demeure malgré les invocations de la fermeture.

Dans un langage orienté objet, les fermetures sont normalement fournies via des objets. Cependant, certains langages OO (par exemple C #) implémentent des fonctionnalités spéciales qui sont plus proches de la définition des fermetures fournies par des langages purement fonctionnels (tels que lisp) qui n'ont pas d'objets pour entourer l'état.

Ce qui est intéressant, c'est que l'introduction de Lambdas et de fermetures en C # rapproche la programmation fonctionnelle de l'utilisation courante.


Alors, pourrait-on dire que les fermetures sont un sous-ensemble de lambdas et les lambdas sont un sous-ensemble de fonctions?
sker

Les fermetures sont un sous-ensemble de lambdas ... mais les lambdas sont plus spéciales que les fonctions normales. Comme je l'ai dit, les lambdas sont définis en ligne. Il n'y a essentiellement aucun moyen de les référencer à moins qu'ils ne soient passés à une autre fonction ou renvoyés comme valeur de retour.
Michael Brown

19
Les lambdas et les fermetures sont chacune un sous-ensemble de toutes les fonctions, mais il n'y a qu'une intersection entre les lambdas et les fermetures, où la partie non intersectée des fermetures serait nommée fonctions qui sont des fermetures et les lamdas sans intersection sont des fonctions autonomes avec des fonctions entièrement variables liées.
Mark Cidade

À mon avis, les lambdas sont des concepts plus fondamentaux que des fonctions. Cela dépend vraiment du langage de programmation.
Wei Qiu

15

C'est aussi simple que cela: lambda est une construction de langage, c'est-à-dire simplement une syntaxe pour des fonctions anonymes; une fermeture est une technique pour l'implémenter - ou toute fonction de première classe, d'ailleurs, nommée ou anonyme.

Plus précisément, une fermeture est la façon dont une fonction de première classe est représentée au moment de l'exécution, comme une paire de son "code" et un environnement "se fermant" sur toutes les variables non locales utilisées dans ce code. De cette façon, ces variables sont toujours accessibles même lorsque les étendues externes d'où elles proviennent ont déjà été fermées.

Malheureusement, il existe de nombreux langages qui ne prennent pas en charge les fonctions en tant que valeurs de première classe, ou ne les prennent en charge que sous une forme paralysée. Ainsi, les gens utilisent souvent le terme «fermeture» pour distinguer «la vraie chose».


12

Du point de vue des langages de programmation, ce sont deux choses complètement différentes.

Fondamentalement, pour un langage complet de Turing, nous n'avons besoin que d'éléments très limités, par exemple l'abstraction, l'application et la réduction. L'abstraction et l'application vous permettent de développer l'expression lamdba, et la réduction détermine la signification de l'expression lambda.

Lambda fournit un moyen d'abstraire le processus de calcul. par exemple, pour calculer la somme de deux nombres, un processus qui prend deux paramètres x, y et renvoie x + y peut être abstrait. Dans le schéma, vous pouvez l'écrire comme

(lambda (x y) (+ x y))

Vous pouvez renommer les paramètres, mais la tâche qu'il termine ne change pas. Dans presque tous les langages de programmation, vous pouvez donner un nom à l'expression lambda, qui sont des fonctions nommées. Mais il n'y a pas beaucoup de différence, ils peuvent être conceptuellement considérés comme du sucre de syntaxe.

OK, imaginez maintenant comment cela peut être mis en œuvre. Chaque fois que nous appliquons l'expression lambda à certaines expressions, par exemple

((lambda (x y) (+ x y)) 2 3)

On peut simplement remplacer les paramètres par l'expression à évaluer. Ce modèle est déjà très puissant. Mais ce modèle ne nous permet pas de changer les valeurs des symboles, par exemple Nous ne pouvons pas imiter le changement de statut. Nous avons donc besoin d'un modèle plus complexe. Pour faire court, chaque fois que nous voulons calculer la signification de l'expression lambda, nous plaçons la paire de symboles et la valeur correspondante dans un environnement (ou tableau). Ensuite, le reste (+ xy) est évalué en recherchant les symboles correspondants dans le tableau. Maintenant, si nous fournissons des primitives pour opérer directement sur l'environnement, nous pouvons modéliser les changements de statut!

Avec ce fond, vérifiez cette fonction:

(lambda (x y) (+ x y z))

Nous savons que lorsque nous évaluons l'expression lambda, xy sera lié dans une nouvelle table. Mais comment et où peut-on chercher z? En fait, z est appelé une variable libre. Il doit y avoir un environnement extérieur qui contient z. Sinon, la signification de l'expression ne peut pas être déterminée en liant uniquement x et y. Pour que cela soit clair, vous pouvez écrire quelque chose comme suit dans le schéma:

((lambda (z) (lambda (x y) (+ x y z))) 1)

Donc, z serait lié à 1 dans une table externe. Nous obtenons toujours une fonction qui accepte deux paramètres mais la vraie signification de celle-ci dépend également de l'environnement extérieur. En d'autres termes, l'environnement externe se ferme sur les variables libres. Avec l'aide de set !, nous pouvons rendre la fonction avec état, c'est-à-dire que ce n'est pas une fonction au sens des mathématiques. Ce qu'il retourne dépend non seulement de l'entrée, mais aussi de z.

C'est quelque chose que vous connaissez déjà très bien, une méthode d'objets repose presque toujours sur l'état des objets. C'est pourquoi certaines personnes disent que «les fermetures sont des objets de pauvre». Mais nous pourrions aussi considérer les objets comme des fermetures de pauvre, car nous aimons vraiment les fonctions de première classe.

J'utilise schéma pour illustrer les idées dues à ce schéma est l'un des premiers langage qui a de vraies fermetures. Tous les documents ici sont beaucoup mieux présentés dans le chapitre 3 du SICP.

Pour résumer, lambda et la fermeture sont des concepts vraiment différents. Un lambda est une fonction. Une fermeture est une paire de lambda et l'environnement correspondant qui ferme la lambda.


Donc, on pourrait remplacer toutes les fermetures par des lambdas imbriqués jusqu'à ce qu'aucune variable libre ne soit plus présente? Dans ce cas, je dirais que les fermetures peuvent être considérées comme des lambdas spéciaux.
Trilarion

8

Le concept est le même que celui décrit ci-dessus, mais si vous venez de l'arrière-plan PHP, cela explique davantage l'utilisation du code PHP.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

fonction ($ v) {return $ v> 2; } est la définition de la fonction lambda. Nous pouvons même le stocker dans une variable, il peut donc être réutilisable:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

Maintenant, que se passe-t-il si vous souhaitez modifier le nombre maximal autorisé dans le tableau filtré? Il faudrait écrire une autre fonction lambda ou créer une fermeture (PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

Une fermeture est une fonction qui est évaluée dans son propre environnement, qui possède une ou plusieurs variables liées auxquelles on peut accéder lorsque la fonction est appelée. Ils viennent du monde de la programmation fonctionnelle, où il y a un certain nombre de concepts en jeu. Les fermetures sont comme des fonctions lambda, mais plus intelligentes dans le sens où elles ont la capacité d'interagir avec des variables de l'environnement extérieur où la fermeture est définie.

Voici un exemple plus simple de fermeture PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Joliment expliqué dans cet article.


7

Cette question est ancienne et a obtenu de nombreuses réponses.
Maintenant, avec Java 8 et Lambda officiel qui sont des projets de fermeture non officiels, cela ravive la question.

La réponse dans le contexte Java (via Lambdas et fermetures - quelle est la différence? ):

"Une fermeture est une expression lambda associée à un environnement qui lie chacune de ses variables libres à une valeur. En Java, les expressions lambda seront implémentées au moyen de fermetures, de sorte que les deux termes sont devenus interchangeables dans la communauté."


Comment les Lamdas sont implémentés par fermeture en Java? Cela signifie-t-il que l'expression Lamdas est convertie en une classe anonyme à l'ancienne?
hackjutsu

5

En termes simples, la fermeture est une astuce sur la portée, lambda est une fonction anonyme. Nous pouvons réaliser la fermeture avec lambda plus élégamment et lambda est souvent utilisé comme paramètre passé à une fonction supérieure


4

Une expression Lambda n'est qu'une fonction anonyme. en simple java, par exemple, vous pouvez l'écrire comme ceci:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

où la classe Function est simplement construite en code java. Vous pouvez maintenant appeler mapPersonToJob.apply(person)quelque part pour l'utiliser. c'est juste un exemple. C'est un lambda avant qu'il n'y ait de syntaxe. Lambdas un raccourci pour cela.

Fermeture:

une Lambda devient une fermeture lorsqu'elle peut accéder aux variables en dehors de cette portée. je suppose que vous pouvez dire sa magie, il peut magiquement envelopper l'environnement dans lequel il a été créé et utiliser les variables en dehors de sa portée (portée externe. donc pour être clair, une fermeture signifie qu'un lambda peut accéder à sa PORTÉE EXTÉRIEURE.

dans Kotlin, un lambda peut toujours accéder à sa fermeture (les variables qui sont dans sa portée externe)


0

Cela dépend si une fonction utilise ou non une variable externe pour effectuer l'opération.

Variables externes - variables définies en dehors de la portée d'une fonction.

  • Les expressions lambda sont sans état car elles dépendent de paramètres, de variables internes ou de constantes pour effectuer des opérations.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Les fermetures conservent leur état car elles utilisent des variables externes (c'est-à-dire des variables définies en dehors de la portée du corps de la fonction) ainsi que des paramètres et des constantes pour effectuer des opérations.

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Lorsque Java crée une fermeture, il conserve la variable n avec la fonction afin qu'elle puisse être référencée lorsqu'elle est passée à d'autres fonctions ou utilisée n'importe où.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.