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?
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?
Réponses:
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 func
ne se ferme pas sur l'environnement dans anotherfunc
- h
n'est pas dé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 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 nonlocal
mot clé .
Un autre point important - func
continuera de fermer anotherfunc
l'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).
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: ►
f x
. f
est la fonction et x
son seul paramètre)λ
(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 . λx.x+2
prend l'expression x+2
et indique que le symbole x
dans cette expression est une variable liée - il peut être remplacé par une valeur que vous fournissez comme paramètre. (λx.x+2) 7
. Ensuite, l'expression (dans ce cas, une valeur littérale) 7
est substituée comme x
dans la sous-expression x+2
du lambda appliqué, donc vous obtenez 7+2
, qui se réduit ensuite à 9
des 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
,.
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 )
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 x
est lié par l'abstraction lambda qui le λx.
précède. Mais l'autre symbole,, y
n'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 y
signifie.
En fait, il en va de même pour les deux autres symboles, 2
et +
. 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:
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+2
le symbole x
est lié, tandis que le symbole y
est libre, par conséquent l'expression est open
et ne peut pas être évaluée à moins que vous ne disiez ce que y
signifie (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
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.
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;
};
}
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.
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».
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.
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();
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é."
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
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)
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ù.