Après avoir lu de nombreux articles expliquant les fermetures ici, il me manque encore un concept clé: Pourquoi écrire une fermeture? Quelle tâche spécifique un programmeur effectuerait-il et qui pourrait être mieux servi par une fermeture?
Des exemples de fermetures dans Swift sont les accès d’un NSUrl et l’utilisation du géocodeur inversé. Voici un exemple. Malheureusement, ces cours ne font que présenter la clôture; ils n'expliquent pas pourquoi la solution de code est écrite comme une fermeture.
Un exemple de problème de programmation dans le monde réel qui pourrait amener mon cerveau à dire «aha, je devrais écrire une clôture pour cela» serait plus informatif qu’une discussion théorique. Les discussions théoriques disponibles sur ce site ne manquent pas.
Réponses:
Tout d'abord, il n'y a rien d'impossible sans l'utilisation de fermetures. Vous pouvez toujours remplacer une fermeture par un objet implémentant une interface spécifique. Ce n'est qu'une question de brièveté et de couplage réduit.
Deuxièmement, gardez à l'esprit que les fermetures sont souvent utilisées de manière inappropriée, où une simple référence de fonction ou une autre construction serait plus claire. Vous ne devriez pas prendre tous les exemples que vous voyez comme une pratique exemplaire.
Les fermetures brillent vraiment par rapport aux autres constructions lorsque vous utilisez des fonctions d'ordre supérieur, lorsque vous avez réellement besoin de communiquer l'état et que vous pouvez en faire une ligne, comme dans cet exemple JavaScript de la page wikipedia sur les fermetures :
Ici,
threshold
est très succinctement et naturellement communiqué de l'endroit où il est défini à l'endroit où il est utilisé. Sa portée est précisément limitée au minimum.filter
Il n'est pas nécessaire d'écrire pour permettre la possibilité de transmettre des données définies par le client comme un seuil. Nous n'avons pas à définir de structures intermédiaires dans le seul but de communiquer le seuil dans cette seule petite fonction. C'est entièrement autonome.Vous pouvez écrire ceci sans fermeture, mais cela nécessitera beaucoup plus de code et sera plus difficile à suivre. En outre, JavaScript a une syntaxe lambda assez détaillée. En Scala, par exemple, le corps entier de la fonction serait:
Si vous pouvez cependant utiliser ECMAScript 6 , grâce aux fonctions de flèche épaisse, même le code JavaScript devient beaucoup plus simple et peut être mis sur une seule ligne.
Dans votre propre code, recherchez les endroits où vous générez beaucoup de passe-passe pour communiquer des valeurs temporaires d'un endroit à un autre. Ce sont d'excellentes opportunités à envisager de remplacer par une fermeture.
la source
bestSellingBooks
code et sur lefilter
code, telles qu'une interface spécifique ou un argument de données utilisateur, pour pouvoir communiquer lesthreshold
données. Cela lie les deux fonctions de manière beaucoup moins réutilisable.En guise d’explication, je vais emprunter du code de cet excellent article de blog sur les fermetures . C'est du JavaScript, mais c'est le langage que la plupart des blogs qui parlent de fermetures utilisent, car les fermetures sont si importantes en JavaScript.
Supposons que vous vouliez rendre un tableau sous forme de tableau HTML. Vous pouvez le faire comme ceci:
Mais vous êtes à la merci de JavaScript quant à la manière dont chaque élément du tableau sera rendu. Si vous voulez contrôler le rendu, vous pouvez faire ceci:
Et maintenant, vous pouvez simplement passer une fonction qui retourne le rendu souhaité.
Que faire si vous souhaitez afficher un total cumulé dans chaque ligne du tableau? Vous auriez besoin d'une variable pour suivre ce total, n'est-ce pas? Une fermeture vous permet d'écrire une fonction de rendu qui se ferme sur la variable du total en cours et vous permet d'écrire un moteur de rendu capable de suivre le total en cours:
La magie qui se passe ici est que l’ accès à la variable est
renderInt
conservétotal
, même s’ilrenderInt
est appelé et sorti à plusieurs reprises.Dans un langage plus traditionnellement orienté objet que JavaScript, vous pouvez écrire une classe contenant cette variable totale et la transmettre au lieu de créer une fermeture. Mais une fermeture est une manière beaucoup plus puissante, propre et élégante de le faire.
Lectures complémentaires
la source
Le but de
closures
est simplement de préserver l' état; d'où le nomclosure
- il ferme sur l'état. Pour faciliter l'explication, j'utilise le langage Javascript.Vous avez généralement une fonction
où la portée de la ou des variables est liée à cette fonction. Donc, après exécution, la variable
txt
sort de la portée. Il n'y a aucun moyen d'y accéder ou de l'utiliser une fois l'exécution de la fonction terminée.Les fermetures sont des constructions de langage, qui permettent - comme dit précédemment - de conserver l’état des variables et donc de prolonger la portée.
Cela pourrait être utile dans différents cas. Un cas d'utilisation est la construction de fonctions d'ordre supérieur .
Voici un exemple simple, mais pas trop utile:
Vous définissez une fonction
makedadder
qui prend un paramètre en entrée et renvoie une fonction . Il existe une fonction externefunction(a){}
et une fonction interne. Enfunction(b){}{}
outre, vous définissez (implicitement) une autre fonctionadd5
à la suite de l'appel de la fonction d'ordre supérieurmakeadder
.makeadder(5)
renvoie une fonction anonyme ( interne ), qui prend à son tour 1 paramètre et renvoie la somme du paramètre de la fonction externe et du paramètre de la fonction interne .L' astuce est que, tout en renvoyant la fonction interne , qui effectue l'addition réelle, la portée du paramètre de la fonction externe (
a
) est préservée.add5
se souvient que le paramètrea
était5
.Ou pour montrer au moins un exemple utile:
Une autre utilisation courante est la dénommée IIFE = expression de fonction immédiatement appelée. Il est très fréquent en javascript pour faux variables membres privées. Cela se fait via une fonction, qui crée une portée privée =
closure
, car elle est immédiatement après la définition invoquée. La structure estfunction(){}()
. Notez les crochets()
après la définition. Cela permet de l'utiliser pour la création d'objets avec un motif de module révélateur . L'astuce consiste à créer une étendue et à renvoyer un objet, qui a accès à cette étendue après l'exécution de l'IIFE.L'exemple d'Addi ressemble à ceci:
L'objet retourné a des références à des fonctions (par exemple
publicSetName
), qui ont à leur tour accès à des variables "privées"privateVar
.Mais ce sont des cas d'utilisation plus spéciaux pour Javascript.
Il y a plusieurs raisons à cela. On pourrait dire que c'est naturel pour lui, puisqu'il suit un paradigme fonctionnel . Ou en Javascript: il est simplement nécessaire de s’appuyer sur les fermetures pour contourner certaines bizarreries du langage.
la source
Il existe deux principaux cas d'utilisation des fermetures:
Asynchronisme. Supposons que vous souhaitiez effectuer une tâche qui prendrait un certain temps, puis que vous agissiez ensuite. Vous pouvez soit faire attendre votre code, ce qui bloque toute exécution ultérieure et empêcher votre programme de répondre, ou appeler votre tâche de manière asynchrone et dire "commencez cette longue tâche en arrière-plan et, une fois terminé, exécutez cette fermeture", où la fermeture contient le code à exécuter quand c'est fait.
Rappels. Ceux-ci sont également appelés "délégués" ou "gestionnaires d'événements" en fonction de la langue et de la plate-forme. L'idée est que vous avez un objet personnalisable qui, à certains points bien définis, exécutera un événement , qui exécute une fermeture transmise par le code qui le configure. Par exemple, dans l'interface utilisateur de votre programme, vous pouvez avoir un bouton et lui donner une fermeture qui contient le code à exécuter lorsque l'utilisateur clique sur le bouton.
Les fermetures ont plusieurs autres utilisations, mais ce sont les deux principales.
la source
Quelques autres exemples:
Tri
La plupart des fonctions de tri fonctionnent en comparant des paires d'objets. Une technique de comparaison est nécessaire. Restreindre la comparaison à un opérateur spécifique signifie une sorte plutôt inflexible. Une meilleure approche consiste à recevoir une fonction de comparaison en tant qu'argument de la fonction de tri. Parfois, une fonction de comparaison sans état fonctionne bien (par exemple, trier une liste de nombres ou de noms), mais que se passe-t-il si la comparaison nécessite un état?
Par exemple, envisagez de trier une liste de villes en fonction de leur distance par rapport à un emplacement spécifique. Une mauvaise solution consiste à stocker les coordonnées de cet emplacement dans une variable globale. Cela rend la fonction de comparaison elle-même sans état, mais au prix d'une variable globale.
Cette approche empêche d'avoir plusieurs threads triant simultanément la même liste de villes en fonction de leur distance à deux emplacements différents. Une fermeture qui entoure l'emplacement résout ce problème et élimine le besoin d'une variable globale.
Nombres aléatoires
L'original
rand()
n'a pris aucun argument. Les générateurs de nombres pseudo-aléatoires ont besoin d'un état. Certains (par exemple, Mersenne Twister) ont besoin de beaucoup d’état. Même le simple mais terriblerand()
état nécessaire. Lisez un article de journal de maths sur un nouveau générateur de nombres aléatoires et vous verrez inévitablement des variables globales. C'est bien pour les développeurs de la technique, pas si bien pour les appelants. Encapsuler cet état dans une structure et la transmettre au générateur de nombres aléatoires est un moyen de résoudre le problème des données globales. C'est l'approche utilisée dans de nombreux langages non-OO pour rendre un générateur de nombres aléatoires réentrant. Une fermeture cache cet état à l'appelant. Une fermeture offre la séquence d'appel simplerand()
et la réentrance de l'état encapsulé.Les nombres aléatoires ne se limitent pas à un simple PRNG. La plupart des gens qui veulent le hasard veulent qu'il soit distribué d'une certaine manière. Je commencerai par des nombres tirés au hasard, compris entre 0 et 1, ou U (0,1) en abrégé. Tout PRNG générant des entiers compris entre 0 et un maximum fera l'affaire; il suffit de diviser (en virgule flottante) l'entier aléatoire par le maximum. Une façon pratique et générique de mettre cela en œuvre consiste à créer une fermeture qui prend une fermeture (le PRNG) et le maximum en tant qu'entrées. Nous avons maintenant un générateur aléatoire générique et facile à utiliser pour U (0,1).
Il existe un certain nombre d'autres distributions en plus de U (0,1). Par exemple, une distribution normale avec un certain écart moyen et standard. Chaque algorithme de générateur de distribution normal que j'ai rencontré utilise un générateur U (0,1). Un moyen pratique et générique de créer un générateur normal consiste à créer une fermeture qui encapsule le générateur U (0,1), la moyenne et l'écart type en tant qu'état. Il s’agit, du moins sur le plan conceptuel, d’une fermeture qui prend une fermeture qui prend pour argument une fermeture.
la source
Les fermetures sont équivalentes aux objets implémentant une méthode run () et inversement, les objets peuvent être émulés avec des fermetures.
L'avantage des fermetures réside dans le fait qu'elles peuvent être utilisées facilement n'importe où vous attendez une fonction: alias fonctions d'ordre supérieur, rappels simples (ou modèle de stratégie). Vous n'avez pas besoin de définir une interface / classe pour créer des fermetures ad-hoc.
L'avantage des objets est la possibilité d'avoir des interactions plus complexes: plusieurs méthodes et / ou différentes interfaces.
Donc, utiliser des fermetures ou des objets est avant tout une question de style. Voici un exemple de choses que les fermetures facilitent mais qu'il est peu pratique de mettre en œuvre avec des objets:
Fondamentalement, vous encapsulez un état masqué auquel vous accédez uniquement par le biais de fermetures globales: vous n'avez pas besoin de faire référence à un objet, utilisez uniquement le protocole défini par les trois fonctions.
Je me fie au premier commentaire de supercat sur le fait que dans certaines langues, il est possible de contrôler précisément la durée de vie des objets, alors que la même chose n'est pas vraie pour les fermetures. Cependant, dans le cas des langages collectés avec ordures, la durée de vie des objets est généralement illimitée et il est donc possible de créer une fermeture pouvant être appelée dans un contexte dynamique où elle ne devrait pas être appelée (lecture d'une fermeture après un flux). est fermé, par exemple).
Cependant, il est assez simple d'empêcher une telle utilisation abusive en capturant une variable de contrôle qui protégera l'exécution d'une fermeture. Plus précisément, voici ce que j'ai en tête (en Common Lisp):
Ici, nous prenons un indicateur de fonction
function
et retournons deux fermetures, chacune capturant une variable locale nomméeactive
:function
, seulement quandactive
est vraiaction
ànil
, akafalse
.Au lieu de
(when active ...)
, il est bien sûr possible d’avoir une(assert active)
expression, ce qui pourrait déclencher une exception si la fermeture est appelée alors qu’elle ne devrait pas l'être. De plus, gardez à l'esprit que le code non sécurisé peut déjà générer une exception lorsqu'il est mal utilisé. Vous avez donc rarement besoin d'un tel wrapper.Voici comment vous l'utiliseriez:
Notez que les fermetures désactivantes pourraient également être attribuées à d'autres fonctions; ici, les
active
variables locales ne sont pas partagées entref
etg
; De plus, en plus deactive
,f
ne fait que se référerobj1
etg
se réfère seulement àobj2
.Supercat a également mentionné que les fermetures peuvent entraîner des fuites de mémoire, mais malheureusement, c’est le cas pour presque tout dans des environnements mal nettoyés. Si elles sont disponibles, cela peut être résolu par des indicateurs faibles (la fermeture elle-même peut être conservée en mémoire, mais n'empêche pas le ramassage des ordures d'autres ressources).
la source
List<T>
dans une (classe hypothétique)TemporaryMutableListWrapper<T>
et l’expose à un code extérieur, on peut s’assurer que, s’il invalide le wrapper, le code extérieur n’aura plus aucun moyen de le manipulerList<T>
. On peut concevoir des fermetures pour permettre l'invalidation une fois qu'elles ont atteint l'objectif recherché, mais ce n'est pas pratique. Les fermetures existent pour rendre certains modèles commodes, et l'effort requis pour les protéger reviendrait à nier cela.Rien de ce qui n'a pas encore été dit, mais peut-être un exemple plus simple.
Voici un exemple JavaScript utilisant des délais d'attente:
Ce qui se passe ici, c’est que lorsqu’on
delayedLog()
appelle, l’appareil revient immédiatement après avoir défini le délai d’expiration, lequel continue de s’écouler en arrière-plan.Mais lorsque le délai imparti est épuisé et que la
fire()
fonction est appelée, la console affiche le textemessage
qui a été initialement transmisdelayedLog()
, car il est toujours disponiblefire()
via la fermeture. Vous pouvez appelerdelayedLog()
autant de fois que vous le souhaitez, avec un message et un délai différents à chaque fois, et tout se passera bien.Mais imaginons que JavaScript ne soit pas fermé.
Une solution serait de rendre le
setTimeout()
blocage - plutôt comme une fonction "sommeil" - afin quedelayedLog()
la portée ne disparaisse pas tant que le délai d’expiration n’est pas écoulé. Mais tout bloquer n'est pas très gentil.Une autre solution consisterait à placer la
message
variable dans une autre portée qui sera accessible aprèsdelayedLog()
la suppression de la portée.Vous pouvez utiliser des variables globales - ou du moins des "portées plus larges" -, mais vous devez savoir comment garder trace de quel message va avec quel délai. Mais il ne peut pas s'agir simplement d'une file d'attente FIFO séquentielle, car vous pouvez définir le délai que vous souhaitez. Donc, cela pourrait être "premier entré, troisième sorti" ou quelque chose. Il vous faut donc un autre moyen pour lier une fonction chronométrée aux variables dont elle a besoin.
Vous pouvez instancier un objet de délai d'attente qui "regroupe" le minuteur avec le message. Le contexte d'un objet est plus ou moins une étendue qui reste. Ensuite, vous feriez exécuter la minuterie dans le contexte de l'objet afin qu'il ait accès au bon message. Mais vous devez stocker cet objet car, sans aucune référence, les ordures seraient collectées (sans les fermetures, il n'y aurait aucune référence implicite non plus). Et vous devriez retirer l'objet une fois que son délai d'attente a été déclenché, sinon il restera dans les parages. Donc, vous auriez besoin d'une sorte de liste d'objets de délai d'expiration, et vérifiez périodiquement si des objets "utilisés" ont été supprimés - ou les objets s'ajouteraient et se supprimeraient de la liste, et ...
Alors ... ouais, ça devient ennuyeux.
Heureusement, vous n'avez pas besoin d'utiliser une portée plus large, ni de démêler des objets pour conserver certaines variables. Parce que JavaScript a des fermetures, vous avez déjà exactement la portée dont vous avez besoin. Une portée qui vous donne accès à la
message
variable quand vous en avez besoin. Et à cause de cela, vous pouvez vous en tirer en écrivantdelayedLog()
comme ci-dessus.la source
message
est inclus dans la portée de la fonctionfire
et est donc référencé dans les appels suivants; mais c'est accidentel pour ainsi dire. Techniquement, c'est une fermeture. +1 de toute façon;)makeadder
exemple ci-dessus qui, à mes yeux, semble être à peu près le même. Vous retournez une fonction "curry" qui prend un seul argument au lieu de deux; en utilisant les mêmes moyens, je crée une fonction qui prend zéro argument. Je juste ne le retourne pas mais le passe à lasetTimeout
place.message
dansfire
génère la fermeture. Et quand il est appelé,setTimeout
il utilise l'état préservé.PHP peut être utilisé pour aider à montrer un exemple réel dans une langue différente.
Donc, fondamentalement, j'enregistre une fonction qui sera exécutée pour l’ URI / api / users . C’est en fait une fonction de middleware qui finit par être stockée sur une pile. D'autres fonctions seront enroulées autour de lui. Un peu comme Node.js / Express.js .
Le conteneur d' injection de dépendance est disponible (via la clause use) dans la fonction lorsqu'elle est appelée. Il est possible de créer une sorte de classe d'action de route, mais ce code s'avère plus simple, plus rapide et plus facile à gérer.
la source
Une fermeture est un morceau de code arbitraire, y compris des variables, pouvant être traité comme des données de première classe.
Un bon exemple trivial est le bon vieux qsort: c'est une fonction pour trier les données. Vous devez lui donner un pointeur sur une fonction qui compare deux objets. Donc, vous devez écrire une fonction. Cette fonction devra peut-être être paramétrée, ce qui signifie que vous lui attribuez des variables statiques. Ce qui signifie que ce n'est pas thread-safe. Vous êtes dans DS. Donc, vous écrivez une alternative qui prend une fermeture au lieu d'un pointeur de fonction. Vous résolvez instantanément le problème de paramétrage car les paramètres deviennent partie intégrante de la fermeture. Vous rendez votre code plus lisible car vous écrivez comment les objets sont comparés directement avec le code qui appelle la fonction de tri.
Il existe une multitude de situations dans lesquelles vous souhaitez effectuer une action nécessitant une grande partie du code de la plaque de la chaudière, plus un élément de code minuscule mais essentiel qui doit être adapté. Vous évitez le code passe-partout en écrivant une fois une fonction qui prend un paramètre de fermeture et fait tout le code autour de lui, puis vous pouvez appeler cette fonction et transmettre le code à adapter en tant que fermeture. Un moyen très compact et lisible d'écrire du code.
Vous avez une fonction dans laquelle un code non trivial doit être exécuté dans de nombreuses situations différentes. Cela produisait soit une duplication de code, soit un code contorsionné, de sorte que le code non trivial ne soit présent qu'une seule fois. Trivial: vous affectez une fermeture à une variable et vous l'appelez de la manière la plus évidente chaque fois que cela est nécessaire.
Multithreading: iOS / MacOS X dispose de fonctions telles que "effectuer cette fermeture sur un fil de fond", "... sur le fil principal", "... sur le fil principal, dans 10 secondes". Le multithreading est trivial .
Appels asynchrones: c'est ce que l'OP a vu. N'importe quel appel qui accède à Internet, ou toute autre chose qui pourrait prendre du temps (comme la lecture des coordonnées GPS) est quelque chose où vous ne pouvez pas attendre le résultat. Vous avez donc des fonctions qui font les choses en arrière-plan, puis vous passez une fermeture pour leur dire quoi faire quand elles sont finies.
C'est un petit début. Cinq situations où les fermetures sont révolutionnaires en termes de production de code compact, lisible, fiable et efficace.
la source
Une fermeture est un moyen abrégé d'écrire une méthode où elle doit être utilisée. Cela vous évite l'effort de déclarer et d'écrire une méthode séparée. C'est utile lorsque la méthode ne sera utilisée qu'une seule fois et que la définition de la méthode est courte. Les avantages sont réduits en tapant car il n’est pas nécessaire de spécifier le nom de la fonction, son type de retour ou son modificateur d’accès. De plus, lors de la lecture du code, vous n'avez pas à chercher ailleurs la définition de la méthode.
Ce qui précède est un résumé de Understand Lambda Expressions de Dan Avidar.
Ceci a clarifié l'utilisation des fermetures pour moi parce que cela clarifie les alternatives (fermeture vs méthode) et les avantages de chacune.
Le code suivant est utilisé une fois, et une fois seulement lors de l'installation. L'écrire en place sous viewDidLoad vous évite d'avoir à le chercher ailleurs et réduit la taille du code.
De plus, il prévoit qu'un processus asynchrone se termine sans bloquer d'autres parties du programme et qu'une fermeture conservera une valeur à réutiliser lors d'appels de fonction ultérieurs.
Une autre fermeture; celui-ci capture une valeur ...
la source