- Fermeture (informatique)
-
Pour les articles homonymes, voir Fermeture.
Dans un langage de programmation, une fermeture ou clôture (en anglais, closure) est une fonction qui capture des références à des variables libres dans l'environnement lexical[1]. Une fermeture est donc créée, entre autres, lorsqu'une fonction est définie dans le corps d'une autre fonction et fait référence à des arguments ou des variables locales à la fonction dans laquelle elle est définie.
Ce procédé est utilisé notamment pour rassembler les exécutions à saisir sur moins de lignes. Ce principe est par exemple celui de la fonction principale (main) sans laquelle le programme entier ne se déclenche pas, ou bien la base de la programmation orientée objet où les classes prédéfinies ne sont pas utilisées à moins d'être appelées[2]. Il arrive aussi qu'on utilise destructeur au sein d'un programme pour assurer la fermeture avec les lignes précédentes[3].
Une fermeture peut être passée en paramètre d'une fonction dans l'environnement où elle a été créée (passée vers le bas) ou renvoyée comme valeur de retour (passée vers le haut). Dans ce cas, le problème posé alors par la fermeture est qu'elle fait référence à des données qui auraient typiquement été allouées dans la pile, et désallouées à la sortie de l'environnement. Hors optimisations par le compilateur, le problème est généralement résolu par une allocation sur le tas de l'environnement.
Sommaire
Exemple de fermeture
La fonction interne ajoute10 a toujours accès à l'argument nombre, bien que l'appel à la fonction ajouteur soit terminé :
En Python
def ajouteur(nombre) : def ajoute(valeur) : return valeur + nombre return ajoute ajoute10 = ajouteur(10) ajoute10(1) # retourne 11
En PHP (depuis 5.3)
<?php function ajouteur($nombre) { // On est obligé d'utiliser une fonction anonyme sinon PHP ne déclare pas // la fonction dans l'environnement actuel mais dans l'environnement global return function($valeur) use($nombre) { return $valeur + $nombre; }; } $ajouter10 = ajouteur(10); $ajouter10(1); // Retourne 11 ?>
Il est important de noter qu'en PHP, une fonction n'a pas accès aux variables de l'environnent où la fonction est déclarée. Pour ce faire il faut utiliser use($nombre) comme ci dessus.
En Javascript
function ajouteur(nombre) { function ajoute(valeur) { return valeur + nombre; } return ajoute; } var ajoute10 = ajouteur(10); ajoute10(1); // retourne 11
En C#
Func<int, int> ajouteur(int nombre) { return valeur => nombre + valeur; } var ajoute10 = ajouteur(10); ajoute10(1); // retourne 11
En C++
En C++ le concept de cloture peut être implémenté avec des structures, comme montré ci desous.
#include <iostream> struct Ajouteur { Ajouteur(int val) : val_(val) {} int val_; int operator () (int rhs) { return val_ + rhs; } }; // version plus simple et infiniement plus efficace template<int Val_> struct AjouteurT { int operator () (int rhs) { return Val_ + rhs; } }; int main(void) { Ajouteur ajout10(10); std::cout << ajout10(2) << std::endl; // affiche 12 (par calcul) AjouteurT<5> ajout5; std::cout << ajout5(1) << std::endl; // affiche 6 (6 existe dans le code objet car le compilateur a tout précalculé) return 0; }
Les clotures peuvent aussi être implémentées à travers des objets de bibliothèques populaires telles que boost. Par exemple boost::function couplé de boost::bind permet d'implémenter une cloture. Des clotures plus simples peuvent aussi être implémentées à travers boost::lambda.
Le concept de cloture est aussi présent dans la meta-programmation (programmation template), on en trouve beaucoup dans boost::mpl. Ceci s'explique du fait que la programmation en langage template se rapproche du paradigme fonctionnel.
Il convient d'être prudent car par définition une cloture peut faire référence a son environnement direct, ici pour gérer ce modèle il faudrait que la cloture soit consciente de son créateur (passage de reference ou de pointeurs). Cela complique la syntaxe et rend dangereux leur utilisation en fonction de leur durée de vie. Il existe de telles clotures dans boost::signals et libsigc++ qui sont capables de savoir quand leur créateur est supprimé, évitant alors de potentielles violations d'accès.En Common Lisp
Exemple, en Common Lisp, d'une fonction qui renvoie un prédicat, vrai si son argument est plus grand qu'un certain minimum :
(defun créer-prédicat-plus-grand-que (min) (lambda (x) (> x min)))
La fermeture créée dans créer-prédicat-plus-grand-que capture dans son environnement lexical la variable min.
En Ruby
def ajouteur(nombre) lambda {|valeur| valeur + nombre} end ajoute10 = ajouteur(10) ajoute10.call(1) # retourne 11
En OCaml
Les mêmes fonctions en OCaml :
let ajouteur n = let ajoute v = n + v in ajoute;; ajoute10 = ajouteur 10;; ajoute10 1;;
Syntaxe spécifique
Grâce à la curryfication, toute fonction peut générer une fermeture lorsqu'on lui passe seulement une partie de ses arguments :
let ajouteur nombre valeur = nombre + valeur;; let ajoute10 = ajouteur 10;; ajoute10 1
Ou encore, étant donné que les opérateurs sont eux-mêmes des fonctions :
let ajoute10 = ( + ) 10;; ajoute10 1
Autres exemples
On peut également donner d'autres exemples :
let creer_predicat_plus_grand_que = function seuil -> (fun x -> x > seuil)
qui donne :
let sup10 = creer_predicat_plus_grand_que 10;; sup10 12;; (* true *) sup10 8;; (* false *)
OCaml permet également de capturer dans une fermeture une valeur modifiable en place (mutable). Par exemple, pour créer un compteur, on définit simultanément 3 fonctions :
let raz, inc, compteur = (* remise à zéro, incrémentation, interrogation *) let n = ref 0 in (function () -> n:=0), (* raz = remise à zéro *) (function () -> n:= !n + 1), (* inc = incrémentation *) (function () -> !n) (* compteur = interrogation *)
la variable mutable n est capturée dans l'environnement commun des 3 fonctions raz, incr et compteur, qui s'utilisent de la sorte :
compteur();; (* renvoie 0 *) inc();; (* incrémente, ne renvoie rien *) compteur();; (* renvoie 1 *) inc();inc();inc();; (* compteur vaut maintenant 4 *) raz();; compteur();; (* renvoie 0 *) n;; (* renvoie "Unbound value n" car n est encapsulée *)
en Haskell
Grâce à la curryfication, en Haskell toute fonction peut générer des closures lorsqu'on lui passe seulement une partie de ses arguments :
ajouteur nombre valeur = nombre + valeur ajoute10 = ajouteur 10 x = ajoute10 1
Ou encore, étant donné que les opérateurs sont eux-mêmes des fonctions :
ajoute10 = (10 +) x = ajoute10 1
en Groovy
En Groovy, une fermeture se débute et se termine par une accolade. La fermeture ajouteur renvoie une fermeture anonyme
def ajouteur = { nombre -> return { valeur -> valeur + nombre } } def ajoute10 = ajouteur(10) assert ajoute10(1) == 11 assert ajoute10 instanceof groovy.lang.Closure
En Scala
def ajouteur(n: Int)(x: Int) = (x + n) def ajoute10 = ajouteur(10);
En C, C++, Objective-C 2.0
Le principe de fermeture a été introduit par Apple au travers des blocs qui sont une extension non standard du C disponible sur Mac OS X à partir de la version 10.6 "Snow Leopard" et sur iOS à partir de la version 4.0[4].
#include <stdio.h> #include <Block.h> typedef int (^AjouteBlock) (int); AjouteBlock ajouteur (int nombre) { return Block_copy( ^ int (int valeur) { return valeur + nombre; }); } int main(void) { AjouteBlock ajoute10 = ajouteur(10); printf("%d",ajoute10(1)); // affiche 11 // Release the block Block_release(ajoute10); return 0; }
En Lua
local ajouteur = function(nombre) return function(valeur) return valeur + nombre end end local ajoute10 = ajouteur(10) ajoute10(1) -- retourne 11
Notes et références
- Wikisource) Sussman and Steele. "Scheme: An interpreter for extended lambda calculus". "... a data structure containing a lambda expression, and an environment to be used when that lambda expression is applied to arguments." (
- http://woufeil.developpez.com/tutoriels/perl/poo/?page=page_5#LV-4
- http://www.generationphp.net/cours/programmation-orientee-objet/
- http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html
Wikimedia Foundation. 2010.