Transparence référentielle

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher

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.

Définition[modifier | modifier le code]

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émoïsation des fonctions référentiellement transparentes (c'est-à-dire la mémorisation des valeurs précédemment calculées).

Exemples et contre-exemples[modifier | modifier le code]

Les opérations arithmétiques sont référentiellement transparentes : on peut ainsi remplacer 5*5 par sa valeur 25. Les fonctions au sens mathématique du terme sont référentiellement transparentes: c'est le cas par exemple de la fonction sin(x) qui est référentiellement transparente, puisqu'elle rend toujours le même résultat pour un x donné.

En revanche, l'expression x++ du langage C n'est pas référentiellement transparente, car elle change la valeur de x. Il est de même de today() qui n'est pas référentiellement transparent, car si on l'évalue aujourd'hui on n'obtient pas le même résultat que si on l'exécute demain.

Contraste avec la programmation impérative[modifier | modifier le code]

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 sont 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 automatiques d'amélioration de code sont 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 peut ê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ée et moins concise 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 dans le cas d'une fonction sans paramètre qui retourne une chaîne saisie au clavier, la valeur de retour dépend de la valeur saisie, donc de multiples appels de la fonction 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 purs, 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ées par des tests, à simplifier un algorithme, à aider à modifier du code sans le casser, à optimiser le code par le moyen de la mémorisation ou éliminer 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érentielle 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érentielle, la différence peut aisément être faite et utilisée dans les programmes.

Principe de séparation commande-requête[modifier | modifier le code]

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

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 que rt(x) = rt(x) aussi longtemps que x contient la même valeur. Par exemple, rt(6) = 7, rt(4) = 5, et ainsi de suite. Mais vous ne pouvez pas dire cela de rq 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() car rq(x) n'est pas nécessairement égal à rq(x)! En effet, la valeur de retour de rq 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[modifier | modifier le code]