Transparence référentielle

Un article de Wikipédia, l'encyclopédie libre.
Sauter à la navigation Sauter à la recherche

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 comportement du programme (c'est-à-dire que le programme a les mêmes effets, les mêmes sorties, les mêmes interactions avec le monde extérieur pour les mêmes entrées, quel que soit son contexte d'exécution). Le terme opposé est reférentiellement opaque.

Si toutes les fonctions impliquées dans l'expression sont des fonctions pures (c'est-à-dire déterministes et 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 4*3 par sa valeur 12 ou par 2*6 ou par 3*4. 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é et n'a aucun effet de bord.

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]

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 (en) sont la fondation théorique de la programmation impérative, et une porte de la sémantique des langages de programmation impératifs.[pas clair]

Néanmoins, puisqu'une expression référentiellement transparente peut toujours être remplacée par sa valeur ou par une expression produisant la même valeur, il n'est pas nécessaire de définir des points de séquence ou de préserver l'ordre d'évaluation. On appelle programmation purement fonctionnelle un paradigme de programmation exempt des soucis d'un ordonnancement figé des exécutions.

L'avantage d'écrire du code 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, inclure une fonction dans une boucle se paye en termes de performance, alors que cette fonction pourrait être déplacée hors de la boucle sans changer les résultats du programme. En déplaçant cette fonction, le programmeur peut y perdre en lisibilité du code. Mais, si le compilateur est capable de déterminer la transparence référentielle de l'appel de la fonction, il effectuera automatiquement ce déplacement produisant ainsi du code plus efficace.

Les langages imposant la transparence référentielle rendent malaisée l'écriture 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 impérative ç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 appel à une fonction qui utilise une variable globale, ou une variable avec une portée dynamique ou lexicale pour aider à calculer ses résultats, n'est pas référentiellement transparente. 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, en revanche l'utilisation de monades permet une programmation fonctionnelle pure tout en ayant un mécanisme qui ressemble de près aux variables globales[1].

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 (comme Haskell) imposent la transparence référentielle pour toutes les fonctions.

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éferentiellement 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 rq est référentiellement opaque, et l'autre rt 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

Dire que rt est référentiellement transparent, signifie que pour un n donné rt(n) produira toujours la même valeur. Par exemple, rt(6) = 7, rt(4) = 5, et ainsi de suite. Mais ce n'est pas vrai de rq car il utilise une variable globale qu'il modifie.

Voir aussi[modifier | modifier le code]

Références[modifier | modifier le code]

  1. En gros, quand on utilise une monade State en Haskell et qu'on l'applique à Int pour obtenir Stat Int, c'est comme s'il y avait une variable globale de type Int, pas une de moins, pas une de plus. On a donc, à tout moment, grâce au type, une connaissance complète et statique des variables globales que l'on utilise.