- Transparence référentielle
-
La transparence référentielle est une propriété des expressions d'un langage de programmation qui fait qu'une expression peut être remplacée par son résultat sans changer le comportement du programme.
Sommaire
Définition
La transparence référentielle est une propriété des expressions d'un programme informatique. Une expression est référentiellement transparente si elle peut être remplacée par sa valeur sans changer le programme (c'est-à-dire résultant en un programme avec les mêmes effets et sorties pour les mêmes entrées). Le terme opposé est reférentiellement opaque.
Si toutes les fonctions impliquées dans l'expression sont des fonctions pures (c'est-à-dire sans effet de bord), alors l'expression est référentiellement transparente. Mais la réciproque est fausse : une expression référentiellement transparente peut impliquer des fonctions impures et donc des effets de bord.
La transparence référentielle est la pierre angulaire de la programmation fonctionnelle. Elle permet notamment la mémorisation des fonctions référentiellement transparentes (c'est-à-dire la mémorisation des valeurs précédemment calculées).
Exemples et contre-exemples
Les opérations arithmétiques sont référentiellement transparentes : on peut ainsi remplacer
5*5
par sa valeur25
.Beaucoup de fonctions, particulièrement celles dans le sens mathématique du terme :
sin(x)
est transparent, puisqu'il donne toujours le même résultat pour unx
donné. L'expressionx++
du langage C n'est pas transparente, car elle change la valeur dex
.Dans la plupart des langages,
print( "Hello world" )
n'est pas transparent, car le remplacer par sa valeur change le comportement du programme puisque "Hello world" n'est pas imprimé.today()
n'est pas transparent, car si vous l'évaluez et le remplacez par sa valeur (disons, "Jan 1, 2001"), vous n'obtenez pas le même résultat que si vous l'exécutez demain.Contraste avec la programmation impérative
Un autre terme pour une fonction référentiellement transparente est fonction déterminée.
Si la substitution d'une expression par sa valeur est valide seulement à certains points d'un programme, alors l'expression n'est pas référentiellement transparente. La définition et l'ordre de ses points de séquence est la fondation théorique de la programmation impérative, et une porte de la sémantique des langages de programmation impératifs.
Néanmoins, puisqu'une expression référentiellement transparente peut être évaluée à tout moment, il n'est pas nécessaire de définir des points de séquence ou la garantie de l'ordre d'évaluation. On appelle programmation purement fonctionnelle la programmation libre de ces considérations.
L'avantage d'écrire du code avec un style référentiellement transparent est que l'analyse de code statique est plus facile et que des transformations d'amélioration de code sont automatiquement possibles. Par exemple, en programmation en C, il y a une pénalité de performance pour l'inclusion d'une fonction dans une boucle, même si l'appel de fonction pourrait être déplacé à l'extérieur de la boucle sans changer les résultats du programme. Le programmeur pourrait être forcé à faire ce déplacement, peut-être aux dépens de la lisibilité du code. Mais, si le compilateur est capable de déterminer si l'appel de la fonction est référentiellement transparent, il peut alors effectuer automatiquement cette transformation.
Le désavantage principal des langages imposant la transparence référentielle est qu'ils rendent malaisé et moins concis l'expression d'opérations qui s'expriment naturellement comme une suite de pas. De tels langages peuvent incorporer des mécanismes pour rendre ces tâches plus faciles tout en retenant leur qualité purement fonctionnelle telles que les monades et les definite clause grammars
Alors qu'en mathématiques toutes les applications de fonction sont référentiellement transparentes, en programmation ça n'est pas toujours le cas. Par exemple, prenez une fonction qui ne prend pas de paramètre et retourne une chaîne saisie au clavier. Un appel à cette fonction pourrait être GetInput(). La valeur de retour de GetInput() dépend de la valeur saisie au clavier, donc de multiples appels de GetInput avec des paramètres identiques (la liste vide) peuvent retourner des résultats différents.
Un exemple plus subtil : une fonction qui utilise une variable globale, ou une variable avec une portée dynamique ou lexical pour aider à calculer ses résultats. Puisque cette variable n'est pas passée comme paramètre mais peut être altérée, les résultats des appels suivants à la fonction peuvent différer même si les paramètres sont identiques. Dans les langages fonctionnels pur, l'affectation n'est pas autorisée. Donc une fonction qui utilise des variables globales ou des portées dynamiques est référentiellement transparente, puisque la valeur des variables ne peut pas changer.
L'importance de la transparence référentielle est qu'elle permet au programmeur ou au compilateur de raisonner sur le comportement du programme. Cela peut aider à prouver qu'il est correct, à trouver des bogues qui ne peuvent être trouvés par des tests, à simplifier un algorithme, à aider à modifier du code sans le casser, à optimiser le code par le moyen de la mémorisation ou l'élimination des sous-expressions communes.
Certains langages de programmation fonctionnelle imposent la transparence référentielle pour toutes les fonctions.
Une conséquence de la transparence référencielle est qu'on ne fait pas ou ne reconnaît pas de différence entre une référence à une chose et la chose elle-même. Sans transparence référencielle, la différence peut aisément être faite et utilisée dans les programmes.
Principe de séparation commande-requête
Eiffel, bien que basé sur la programmation impérative, impose une séparation stricte entre les commandes qui peuvent produire des effets de bord et les requêtes qui doivent être référentiellement transparentes. Les requêtes retournent un résultat mais ne changent pas l'environnement. On appelle cette règle le principe de séparation commande-requête qui permet d'aboutir à un style qui sépare clairement les parties référentiellement transparentes des autres. Par exemple dans la manipulation de listes :
my_list.finish -- Déplace le curseur à la fin de la liste value := my_list.item -- Obtient la valeur à la position du curseur : référentiellement transparent
Cela affecte même des fonctionnalités impératives comme les entrées :
my_file.read_integer -- obtient le prochain entier; effet de bord, mais pas de valeur de retour value := my_file.last_integer -- obtient le dernier entier lu: réferentiellent transparent
Appeler plusieurs fois en série
last_integer
donne le même résultat à chaque fois.Autre exemple
Soit deux fonctions, dont l'une est référentiellement opaque, et l'autre référentiellement transparente :
globalValue = 0; integer function rq(integer x) begin globalValue = globalValue + 1; return x + globalValue; end integer function rt(integer x) begin return x + 1; end
Maintenant,
rt
est référentiellement transparent, ce qui signifie quert(x) = rt(x)
aussi longtemps quex
contient la même valeur. Par exemple,rt(6) = 7, rt(4) = rt(3+1) = 5
, et ainsi de suite. Mais vous ne pouvez pas dire cela derq
car il utilise une variable globale qu'il modifie.Pourquoi est-ce une mauvaise chose ? Supposons maintenant que nous voulions raisonner sur le code suivant :
integer p = rq(x) + rq(y) * (rq(x) - rq(x));
A priori, on pourrait être tenté de simplifier cette ligne de code ainsi :
integer p = rq(x) + rq(y) * (0) = integer p = rq(x) + 0 = integer p = rq(x);
Mais cela ne marcherait pas pour
rq()
carrq(x)
n'est pas nécessairement égal àrq(x)
! En effet, la valeur de retour derq
est basée sur une variable globale (dont la valeur n'est pas passée comme paramètre) et qui peut être modifiée.Cela fonctionnerait néanmoins avec
rt
, qui est une fonction référentiellement transparente.On peut donc appliquer des raisonnements mathématiques avancés au code, ce qui conduit à des programmes plus robustes et permet de trouver des bogues qui échapperaient aux tests classiques. Cela permet même d'identifier des opportunités d'optimisation.
Voir aussi
Wikimedia Foundation. 2010.