La transparence référentielle, associée à une fonction, indique que vous pouvez déterminer le résultat de l'application de cette fonction uniquement en consultant les valeurs de ses arguments. Vous pouvez écrire des fonctions référentiellement transparentes dans n’importe quel langage de programmation, par exemple Python, Scheme, Pascal, C.
Par ailleurs, dans la plupart des langues, vous pouvez également écrire des fonctions non transparentes par référentiel. Par exemple, cette fonction Python:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
n'est pas référentiellement transparent, appelant en fait
foo(x) + foo(x)
et
2 * foo(x)
produira des valeurs différentes, pour tout argument x
. La raison en est que la fonction utilise et modifie une variable globale. Par conséquent, le résultat de chaque invocation dépend de cet état changeant, et pas seulement de l'argument de la fonction.
Haskell, un langage purement fonctionnel, sépare strictement l' évaluation de l'expression dans laquelle des fonctions pures sont appliquées et qui est toujours transparente de manière référentielle, de l' exécution de l' action (traitement des valeurs spéciales), qui n'est pas transparente de manière référentielle, c'est-à-dire que l'exécution de la même action peut avoir à chaque fois une résultat différent.
Donc, pour toute fonction Haskell
f :: Int -> Int
et tout entier x
, il est toujours vrai que
2 * (f x) == (f x) + (f x)
Un exemple d'action est le résultat de la fonction de bibliothèque getLine
:
getLine :: IO String
À la suite de l'évaluation de l'expression, cette fonction (en fait une constante) produit tout d'abord une valeur pure de type IO String
. Les valeurs de ce type sont des valeurs comme les autres: vous pouvez les transmettre, les placer dans des structures de données, les composer à l'aide de fonctions spéciales, etc. Par exemple, vous pouvez faire une liste d'actions comme ceci:
[getLine, getLine] :: [IO String]
Les actions ont ceci de particulier que vous pouvez demander à l'exécution de Haskell de les exécuter en écrivant:
main = <some action>
Dans ce cas, lorsque votre programme Haskell est démarré, le moteur d'exécution parcourt l'action qui lui est liée main
et l' exécute , produisant éventuellement des effets secondaires. Par conséquent, l'exécution de l'action n'est pas transparente de manière référentielle, car l'exécution de la même action à deux reprises peut produire des résultats différents selon ce que le moteur d'exécution obtient en entrée.
Grâce au système de types de Haskell, une action ne peut jamais être utilisée dans un contexte où un autre type est attendu, et inversement. Donc, si vous voulez trouver la longueur d'une chaîne, vous pouvez utiliser la length
fonction:
length "Hello"
retournera 5. Mais si vous voulez trouver la longueur d'une chaîne lue depuis le terminal, vous ne pouvez pas écrire
length (getLine)
parce que vous obtenez une erreur de type: length
attend une entrée de type list (et une chaîne est, en réalité, une liste) mais getLine
une valeur de type IO String
(une action). De cette manière, le système de types garantit qu'une valeur d'action telle que getLine
(dont l'exécution est exécutée en dehors du langage principal et qui peut être transparente de manière non référentielle) ne peut pas être cachée à l'intérieur d'une valeur de type non-action Int
.
MODIFIER
Pour répondre à cette question, voici un petit programme Haskell qui lit une ligne à partir de la console et en imprime la longueur.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
L'action principale consiste en deux sous-actions exécutées séquentiellement:
getline
de type IO String
,
- la seconde est construite en évaluant la fonction
putStrLn
de type String -> IO ()
sur son argument.
Plus précisément, la deuxième action est construite par
- liant
line
à la valeur lue par la première action,
- évaluer les fonctions pures
length
(calculer la longueur sous forme d'entier) puis show
(transformer l'entier en chaîne),
- construire l'action en appliquant une fonction
putStrLn
au résultat de show
.
À ce stade, la deuxième action peut être exécutée. Si vous avez tapé "Bonjour", "5" sera imprimé.
Notez que si vous obtenez une valeur d'une action en utilisant la <-
notation, vous ne pouvez utiliser cette valeur que dans une autre action, par exemple, vous ne pouvez pas écrire:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
car show (length line)
a le type String
alors que la notation do nécessite qu'une action ( getLine
de type IO String
) soit suivie d'une autre action (par exemple putStrLn (show (length line))
de type IO ()
).
EDIT 2
La définition de la transparence référentielle de Jörg W Mittag est plus générale que la mienne (j'ai voté pour sa réponse). J'ai utilisé une définition restreinte parce que l'exemple de la question porte sur la valeur de retour des fonctions et je voulais illustrer cet aspect. Cependant, RT fait généralement référence à la signification de l'ensemble du programme, y compris les changements d'état global et les interactions avec l'environnement (IO) provoqués par l'évaluation d'une expression. Donc, pour une définition correcte et générale, vous devriez vous référer à cette réponse.