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


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

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


679
2017-10-21 03:12


origine


Réponses:


UNE lambda est juste une fonction anonyme - une fonction définie sans nom. Dans certaines langues, telles que Scheme, elles sont équivalentes 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 langues, comme Python, il y a des distinctions (plutôt inutiles) entre eux, mais ils se comportent de la même façon sinon.

UNE fermeture est une fonction qui se ferme la environnement dans lequel il a été défini. 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 func ne fait pas fermer l'environnement dans anotherfunc - h est indéfini. func ne ferme que sur l'environnement mondial. Cela fonctionnera:

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

Parce qu'ici, func est défini dans anotherfunc, et en python 2.3 et plus (ou un certain nombre comme ça) quand ils presque obtenu des fermetures correctes (la mutation ne fonctionne toujours pas), cela signifie qu'il se ferme  anotherfuncl'environnement et peut accéder aux variables à l'intérieur de celui-ci. En Python 3.1+, la mutation fonctionne aussi en utilisant la nonlocal mot-clé.

Un autre point important - func continuera à fermer anotherfuncl'environnement même quand il n'est plus évalué dans anotherfunc. Ce code fonctionnera également:

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

print anotherfunc(10)()

Cela va imprimer 10.

Ceci, comme vous le remarquez, n'a rien à voir avec lambdas - ils sont deux concepts différents (bien que liés).


605
2017-10-21 03:58



Quand la plupart des gens pensent à les fonctions, ils pensent à 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 expressions lambda, vous pouvez avoir 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é assigné:

foo();

Plus utiles que d'attribuer des fonctions anonymes à des variables, cependant, les transmettent vers ou depuis des fonctions d'ordre supérieur, c'est-à-dire des fonctions qui acceptent / retournent d'autres fonctions. Dans beaucoup de ces cas, nommer une fonction n'est pas nécessaire:

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 est connue comme telle quand elle se referme sur des variables dans la portée où la fonction est définie, c'est-à-dire que la fermeture se référera à l'environnement avec les variables externes utilisées dans la 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 si tout cela était dans une autre fonction et vous avez passé 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 l'appellation "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;
           };
 }

164
2017-10-21 03:46



Il y a beaucoup de confusion autour des lambdas et des fermetures, même dans les réponses à cette question de StackOverflow ici. Au lieu de demander à des programmeurs au hasard qui ont appris à propos des fermetures de la pratique avec certains langages de programmation ou d'autres programmeurs clueless, faites un voyage à la la source (où tout a commencé). Et depuis que les lambdas et les fermetures viennent de Lambda Calculus inventé par Alonzo Church dans les années 30 avant même que les premiers ordinateurs électroniques existent, c'est le la source Je parle de.

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

  • APPLICATION: Appliquer une expression à une autre, notée f x.
    (Pensez-y comme un appel de fonction, où f est la fonction et x est son seul paramètre)
  • ABSTRACTION: Lie un symbole apparaissant dans une expression pour marquer que ce symbole est juste un "slot", une boîte vide en attente d'être remplie avec une valeur, une "variable" pour ainsi dire. Il est fait en ajoutant une lettre grecque λ (lambda), puis le nom symbolique (par ex. x), puis un point . avant l'expression. Cela convertit ensuite l'expression en un fonction attendre un paramètre.
    Par exemple: λx.x+2 prend l'expression x+2 et dit que le symbole x dans cette expression est un variable liée - Il peut être remplacé par une valeur que vous fournissez en tant que paramètre.
    Notez que la fonction définie de cette manière est anonyme - il n'a pas de nom, donc vous ne pouvez pas encore vous y référer, mais vous pouvez appeler immédiatement il (souvenez-vous de l'application?) en lui fournissant le paramètre qu'il attend, comme ceci: (λx.x+2) 7. Puis l'expression (dans ce cas une valeur littérale) 7 est substitué comme x dans la sous-expression x+2 de la lambda appliquée, de sorte que vous obtenez 7+2, qui ensuite réduit à 9 par des règles arithmétiques communes.

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


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

function(x) { return x+2; }

et vous pouvez immédiatement l'appliquer à 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; }

ce 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 pouvez l'appeler immédiatement:

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

Dans LISP, les lambdas sont faits 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, maintenant il est temps de résoudre l'autre mystère: qu'est-ce qu'un fermeture. Pour ce faire, parlons de symboles (variables) dans les expressions lambda.

Comme je l'ai dit, ce que fait l'abstraction lambda est contraignant un symbole dans sa sous-expression, de sorte qu'il devient un substitut paramètre. Un tel symbole est appelé lié. Mais que faire s'il y a d'autres symboles dans l'expression? Par exemple: λx.x/y+2. Dans cette expression, le symbole x est lié par l'abstraction lambda λx.le précédant. Mais l'autre symbole, y, n'est pas lié - c'est gratuit. Nous ne savons pas ce que c'est et d'où il vient, donc nous ne savons pas ce que c'est veux dire et quoi valeur il représente, et donc nous ne pouvons pas évaluer cette expression jusqu'à ce que nous comprenions ce que y veux dire.

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

Vous pouvez penser à la gratuit des symboles tels que définis ailleurs, en dehors de l'expression, dans son «contexte environnant», qu'on appelle son environnement. L'environnement pourrait être une plus grande expression que cette expression est une partie de (comme Qui-Gon Jinn a dit: "Il y a toujours un plus gros poisson";)), ou dans une bibliothèque, ou dans la langue elle-même (comme primitif).

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

  • Expressions fermées: tout symbole apparaissant dans ces expressions est lié par une abstraction lambda. En d'autres termes, ils sont autonome; ils ne nécessitent aucun contexte environnant pour être évalués. Ils sont aussi appelés combinateurs.
  • OPEN expressions: certains symboles de ces expressions ne sont pas lié - c'est-à-dire que certains des symboles qui s'y trouvent sont gratuit et ils 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 un ouvrir expression lambda en fournissant le 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 le fermeture partie:
le fermeture d'un expression lambda est cet ensemble particulier de symboles définis dans le contexte externe (environnement) qui donnent des valeurs à la symboles libres dans cette expression, les rendant non-libres plus. Il tourne un ouvrir expression lambda, qui contient encore des symboles libres "indéfinis", dans un fermé un, qui n'a plus de symboles libres.

Par exemple, si vous avez l'expression lambda suivante: λx.x/y+2, le symbole x est lié, tandis que le symbole y est libre, donc l'expression est open et ne peut être évalué que si vous dites ce que y des moyens (et la même chose avec + et 2, qui sont également gratuits). Mais supposons que vous ayez aussi environnement comme ça:

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

Ce environnement fournit des définitions pour tous les symboles «non définis» (libres) 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 le fermeture de notre expression lambda:>

En d'autres termes, il se ferme une expression lambda ouverte. C'est là que le nom fermeture vient d'abord, et c'est pourquoi les réponses de tant de gens dans ce fil ne sont pas tout à fait correct: P


Alors pourquoi se trompent-ils? Pourquoi tant d'entre eux disent que les fermetures sont des structures de données en mémoire, ou certaines caractéristiques des langues qu'ils utilisent, ou pourquoi confondent-ils 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 "fermetures" ce qui est supposé être juste des lambdas. Ou ils appellent «fermetures» une technique particulière qu'ils ont utilisée pour implémenter le cadrage lexical, 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 "enferme" ces variables, c'est-à-dire, les capture dans une structure de données pour les empêcher d'être détruites après l'exécution de la fonction externe. Mais ceci est juste inventé post factum l'étymologie du folklore et le marketing, ce qui ne fait que rendre les choses plus confuses, car chaque fournisseur de langue utilise sa propre terminologie.

Et c'est encore pire à cause du fait qu'il y a toujours un peu de vérité dans ce qu'ils disent, ce qui ne vous permet pas de l'écarter facilement comme faux: P Laissez-moi vous expliquer:

Si vous voulez implémenter un langage qui utilise lambdas en tant que citoyen de première classe, vous devez lui permettre d'utiliser des symboles définis dans son contexte (c'est-à-dire utiliser des variables libres dans votre lambdas). Et ces symboles doivent être là même quand 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 d'appels), qui ne sera plus là quand la fonction reviendra. Par conséquent, pour qu'un lambda fonctionne comme vous le souhaitez, vous devez en quelque sorte "capturer" toutes ces variables libres de son contexte externe et les sauvegarder pour plus tard, même lorsque le contexte externe aura disparu. Autrement dit, vous devez trouver le fermeture de votre lambda (toutes ces variables externes qu'il utilise) et stockez-le ailleurs (soit en faisant une copie, soit en leur préparant un espace à l'avance, 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 est le fermeture, qui est l'ensemble des variables libres du environnement de votre lambda qui doit être sauvé 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 mettre en œuvre 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 passées comme paramètres à d'autres fonctions, renvoyées par des fonctions, et stockées dans des variables, pour représenter lambdas, et leur permettant d'accéder à leur environnement enfermant ainsi que le code machine pour fonctionner dans ce contexte. Mais c'est juste un moyen (parmi tant d'autres) mettre en place fermeture, non la 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 dans son environnement qui donnent des valeurs aux variables libres contenues dans cette expression lambda, effectivement fermeture l'expression (en tournant un ouvrir expression lambda, qui ne peut pas encore être évaluée, fermé expression lambda, qui peut ensuite être évaluée, puisque tous les symboles qui y sont contenus sont maintenant définis).

Tout le reste n'est qu'un «culte de la cargaison» et une «magie voo-doo» de programmeurs et de vendeurs de langues qui ne sont pas conscients des véritables racines de ces notions.

J'espère que cela répond à vos questions. Mais si vous avez des questions de suivi, n'hésitez pas à les poser dans les commentaires, et j'essaierai de mieux vous l'expliquer.


146
2018-04-27 01:18



Toutes les fermetures ne sont pas des lambdas et tous les lambdas ne sont pas des fermetures. Les deux sont des fonctions, mais pas nécessairement de la manière dont nous avons l'habitude de le savoir.

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

Une fermeture est une fonction qui entoure son état environnant en référençant des champs extérieurs à son corps. L'état fermé reste à travers les invocations de la fermeture.

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

Ce qui est intéressant, c'est que l'introduction de Lambdas et Closures en C # rapproche la programmation fonctionnelle de l'usage courant.


51
2017-10-21 03:29



C'est aussi simple que ceci: 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 comment un fonction de première classe est représenté à l'exécution, comme une paire de son "code" et un environnement "closing" sur toutes les variables non-locales utilisées dans ce code. De cette façon, ces variables sont toujours accessibles même lorsque les portées externes d'où elles proviennent ont déjà été supprimées.

Malheureusement, il existe de nombreuses langues 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 infirme. Donc, les gens utilisent souvent le terme «fermeture» pour distinguer «la vraie chose».


13
2018-03-19 08:31



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

Fondamentalement, pour un langage complet de Turing, nous avons seulement besoin d'éléments très limités, par ex. abstraction, application et réduction. L'abstraction et l'application fournissent la façon dont vous pouvez construire l'expression lamdba, et la réduction dérive la signification de l'expression lambda.

Lambda fournit un moyen de résumer 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 extrait. Dans le schéma, vous pouvez l'écrire comme

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

Vous pouvez renommer les paramètres, mais la tâche terminée 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 de la simple syntaxe du sucre.

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

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

Nous pouvons simplement substituer les paramètres à 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 ex. 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 mettons la paire de symbole et la valeur correspondante dans un environnement (ou une table). Ensuite, le reste (+ x y) est évalué en recherchant les symboles correspondants dans le tableau. Maintenant, si nous fournissons des primitives pour fonctionner directement sur l'environnement, nous pouvons modéliser les changements de statut!

Avec cet arrière-plan, vérifiez cette fonction:

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

Nous savons que lorsque nous évaluons l'expression lambda, x y sera lié dans une nouvelle table. Mais comment et où pouvons-nous chercher z? En fait, z est appelé une variable libre. Il doit y avoir un extérieur un environnement qui contient z. Sinon, la signification de l'expression ne peut pas être déterminée en liant uniquement x et y. Pour le rendre 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 signification réelle dépend aussi de l'environnement extérieur. En d'autres termes, l'environnement extérieur se referme sur les variables libres. Avec l'aide de set !, nous pouvons rendre la fonction stateful, c'est-à-dire, ce n'est pas une fonction dans le sens des maths. Ce que cela renvoie ne dépend pas seulement de l'entrée, mais aussi de z.

C'est quelque chose que vous savez 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 du pauvre». Mais nous pourrions aussi considérer les objets comme des fermetures de pauvres parce que nous aimons vraiment les fonctions de première classe.

J'utilise schéma pour illustrer les idées en raison de ce schéma est l'un des premiers langages qui a de véritables fermetures. Tous les matériaux ici sont beaucoup mieux présentés dans le chapitre 3 de SICP.

En résumé, lambda et la fermeture sont des concepts très différents. Un lambda est une fonction. Une fermeture est une paire de lambda et l'environnement correspondant qui ferme le lambda.


12
2018-03-19 00:11



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

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

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

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

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

Maintenant, que faire si vous voulez changer le nombre maximum autorisé dans le tableau filtré? Vous devrez é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 il est possible d'accéder lorsque la fonction est appelée. Ils viennent du monde de la programmation fonctionnelle, où un certain nombre de concepts sont en jeu. Les fermetures sont comme les 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 la fermeture de PHP:

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

$closure();

Bien expliqué dans cet article. 


7
2017-07-31 07:41



Cette question est ancienne et a beaucoup de réponses. Maintenant, avec Java 8 et Official Lambda qui sont des projets de fermeture non officiels, il revit la question.

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

"Une fermeture est une expression lambda couplée à un environnement qui lie chacune de ses variables libres à une valeur.En Java, les expressions lambda seront mises en œuvre au moyen de fermetures, de sorte que les deux termes sont utilisés de façon interchangeable dans la communauté."


6
2017-11-09 17:46



Simplement parlant, 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
2018-05-16 07:03