Fermeture (informatique)

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher
Page d'aide sur l'homonymie 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.

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.

Exemple de fermeture[modifier | modifier le code]

La fonction interne ajoute10 a toujours accès à l'argument nombre, bien que l'appel à la fonction ajouteur soit terminé :

En Perl[modifier | modifier le code]

sub ajouteur {
  my $valeur = shift;
  return sub { shift() + $valeur };
}
 
my $ajoute10 = ajouteur(10);
$ajoute10->( 1 ); # retourne 11

En Python[modifier | modifier le code]

def ajouteur(nombre):
    def ajoute(valeur):
        return valeur + nombre
    return ajoute
 
ajoute10 = ajouteur(10)
ajoute10(1) # retourne 11

En PHP (depuis 5.3)[modifier | modifier le code]

<?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'environnement où la fonction est déclarée. Pour ce faire il faut utiliser use($nombre) comme ci-dessus.

En Javascript[modifier | modifier le code]

function ajouteur(nombre) {
  function ajoute(valeur) {
    return valeur + nombre;
  }
 
  return ajoute;
}
 
var ajoute10 = ajouteur(10);
ajoute10(1); // retourne 11

En Go[modifier | modifier le code]

func ajouteur(nombre int) func(int) int {
  return func(valeur int) int {
    return nombre + valeur
  }
}
 
ajoute10 := ajouteur(10)
ajoute10(1) // retourne 11

En C#[modifier | modifier le code]

Func<int, int> ajouteur(int nombre)
{
    return valeur => nombre + valeur;
}
 
var ajoute10 = ajouteur(10);
ajoute10(1); // retourne 11

En C++[modifier | modifier le code]

#include <functional>
 
int main()
{
  auto ajouteur = [] (int i) -> std::function<int(int)>
  { return ([=] (int j) { return i + j; }); };
  auto ajoute10 = ajouteur(10);
  ajoute10(1); // retourne 11
  return 0;
}

Avant C++11[modifier | modifier le code]

Avant C++11 le concept de clôture pouvait être implémenté avec des structures, comme montré ci-dessous.

#include <iostream>
struct Ajouteur
{
	Ajouteur(int val)
	: val_(val)
	{}
 
	int val_;
 
	int operator () (int rhs)
	{
		return val_ + rhs;
	}
};
 
// version utilisant les templates, en supposant que le paramètre 'val' est connu à la compilation
// si ce n'est pas le cas, les templates sont inutiles et l'autre version est la seule possible
 
template<int Val_>
struct AjouteurT
{
	int operator () (int rhs)
	{
		return Val_ + rhs;
	}
};
 
int main(void)
{
	Ajouteur ajout10(10);
	std::cout << ajout10(2) << std::endl; 
        // si les optimisations sont désactivées, calcule 12 en additionnant deux variables
 
	AjouteurT<5> ajout5;
	std::cout << ajout5(1) << std::endl; 
        //  si les optimisations sont désactivées, calcule 6 en additionnant une constante (5) avec une variable
	return 0;
}

D’une manière générale, les templates permettent, en plus d'une écriture allégée dans certains cas bien spécifiques, de propager explicitement les constantes, et donc de certifier au compilateur que certains calculs peuvent et doivent être faits dès la compilation sans inquiétude de possibles effets de bord.


Les clôtures 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 clôture. Des clôtures plus simples peuvent aussi être implémentées à travers boost::lambda.

Le concept de clôture 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.

Cependant par définition une clôture peut faire référence à son environnement direct, ici la gestion de ce modèle nécessiterait que l’instruction appelante transmette la porté de son propre environnement, par exemple par un passage de référence ou de pointeurs. Cela complique la syntaxe et rend dangereux leur utilisation en fonction de leur durée de vie. Il existe de telles clôtures dans boost::signals et libsigc++ qui sont capables de savoir quand la structure appelante est supprimé, évitant alors de potentielles violations d'accès.

En Common Lisp[modifier | modifier le code]

(defun ajouteur (nombre)
  (lambda (valeur)
    (+ nombre valeur)))
 
(defvar +10 (ajouteur 10))
(funcall +10 1) ; retourne 11

En Ruby[modifier | modifier le code]

def ajouteur(nombre)
  lambda {|valeur| valeur + nombre}
end
 
ajoute10 = ajouteur(10)
ajoute10.call(1) # retourne 11

En OCaml[modifier | modifier le code]

Les mêmes fonctions en OCaml :

let ajouteur n =
  let ajoute v = n + v in
  ajoute;;
 
let ajoute10 = ajouteur 10;;
ajoute10 1;;

Syntaxe spécifique[modifier | modifier le code]

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[modifier | modifier le code]

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[modifier | modifier le code]

Grâce à la curryfication, en Haskell toute fonction peut générer des fermetures 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[modifier | modifier le code]

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[modifier | modifier le code]

def ajouteur(n: Int)(x: Int) = (x + n)
 
def ajoute10 = ajouteur(10)_

En C, C++, Objective-C 2.0[modifier | modifier le code]

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[2].

#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[modifier | modifier le code]

local ajouteur = function(nombre)
  return function(valeur)
    return valeur + nombre
  end
end
 
local ajoute10 = ajouteur(10)
ajoute10(1) -- retourne 11

En Delphi[modifier | modifier le code]

type
  TAjoute = reference to function(valeur: Integer): Integer;
 
function ajouteur(nombre: Integer): TAjoute;
begin
  Result := function(valeur: Integer): Integer
  begin
    Result := valeur + nombre;
  end;
end;
 
var
  ajoute10: TAjoute;
begin
  ajoute10 := ajouteur(10);
  ajoute10(1); // retourne 11
end.

En Powershell[modifier | modifier le code]

function ajouteur($nombre)
{
    {
    param($valeur)
    $valeur+$nombre
    }.GetNewClosure()
}
 
$ajoute10 = ajouteur 10
&$ajoute10 1 # retourne 11

Notes et références[modifier | modifier le code]

  1. 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." (Wikisource)
  2. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html