Referenzielle Transparenz, auf eine Funktion bezogen, gibt an, dass Sie das Ergebnis der Anwendung dieser Funktion nur anhand der Werte ihrer Argumente bestimmen können. Sie können referenziell transparente Funktionen in jeder Programmiersprache schreiben, z. B. Python, Scheme, Pascal, C.
Andererseits können Sie in den meisten Sprachen auch nicht referenziell transparente Funktionen schreiben. Zum Beispiel diese Python-Funktion:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
ist nicht referenziell transparent, in der Tat aufrufen
foo(x) + foo(x)
und
2 * foo(x)
erzeugt für jedes Argument unterschiedliche Werte x
. Der Grund dafür ist, dass die Funktion eine globale Variable verwendet und ändert. Daher hängt das Ergebnis jedes Aufrufs von diesem sich ändernden Zustand ab und nicht nur vom Argument der Funktion.
Haskell, eine rein funktionale Sprache, trennt die Ausdrucksbewertung, in der reine Funktionen angewendet werden und die immer referenziell transparent ist , strikt von der Aktionsausführung (Verarbeitung von Sonderwerten), die nicht referenziell transparent ist, dh die jeweils gleiche Aktion ausführen kann anderes Ergebnis.
Also für jede Haskell-Funktion
f :: Int -> Int
und jede ganze Zahl x
, es ist immer wahr, dass
2 * (f x) == (f x) + (f x)
Ein Beispiel für eine Aktion ist das Ergebnis der Bibliotheksfunktion getLine
:
getLine :: IO String
Diese Funktion (eigentlich eine Konstante) liefert als Ergebnis der Ausdrucksauswertung zunächst einen reinen Wert vom Typ IO String
. Werte dieses Typs sind Werte wie alle anderen: Sie können sie weitergeben, in Datenstrukturen einfügen, mit speziellen Funktionen zusammenstellen usw. Zum Beispiel können Sie eine Liste von Aktionen wie folgt erstellen:
[getLine, getLine] :: [IO String]
Aktionen sind insofern besonders, als Sie der Haskell-Laufzeit anweisen können, sie auszuführen, indem Sie Folgendes schreiben:
main = <some action>
In diesem Fall durchläuft die Laufzeit beim Starten Ihres Haskell-Programms die daran gebundene Aktion main
und führt sie aus, wobei möglicherweise Nebenwirkungen auftreten. Daher ist die Aktionsausführung nicht referenziell transparent, da die zweimalige Ausführung derselben Aktion je nach dem, was die Laufzeit als Eingabe erhält, zu unterschiedlichen Ergebnissen führen kann.
Dank des Typsystems von Haskell kann eine Aktion niemals in einem Kontext verwendet werden, in dem ein anderer Typ erwartet wird, und umgekehrt. Wenn Sie also die Länge eines Strings ermitteln möchten, können Sie die folgende length
Funktion verwenden:
length "Hello"
gibt 5 zurück. Wenn Sie jedoch die Länge eines vom Terminal gelesenen Strings ermitteln möchten, können Sie nicht schreiben
length (getLine)
weil Sie einen Typfehler erhalten: length
Erwartet eine Eingabe von Typ Liste (und ein String ist in der Tat eine Liste), getLine
ist aber ein Wert von Typ IO String
(eine Aktion). Auf diese Weise stellt das Typsystem sicher, dass ein Aktionswert wie getLine
(dessen Ausführung außerhalb der Kernsprache erfolgt und der möglicherweise nicht referenziell transparent ist) nicht in einem Nicht-Aktionswert vom Typ verborgen werden kann Int
.
BEARBEITEN
Um eine exakte Frage zu beantworten, ist hier ein kleines Haskell-Programm, das eine Zeile von der Konsole liest und ihre Länge ausgibt.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
Die Hauptaktion besteht aus zwei Unteraktionen, die nacheinander ausgeführt werden:
getline
vom Typ IO String
,
- Die zweite wird konstruiert, indem die Funktion
putStrLn
des Typs String -> IO ()
in ihrem Argument ausgewertet wird .
Genauer gesagt wird die zweite Aktion von erstellt
- Bindung
line
an den von der ersten Aktion gelesenen Wert,
- die reinen Funktionen auswerten
length
(Länge als ganze Zahl berechnen) und dann show
(die ganze Zahl in eine Zeichenkette verwandeln),
- Aufbau der Aktion durch Anwenden der Funktion
putStrLn
auf das Ergebnis von show
.
An diesem Punkt kann die zweite Aktion ausgeführt werden. Wenn Sie "Hallo" eingegeben haben, wird "5" ausgegeben.
Beachten Sie, dass Sie, wenn Sie einen Wert aus einer Aktion mit der <-
Notation erhalten, diesen Wert nur in einer anderen Aktion verwenden können, z. B. können Sie nicht schreiben:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
weil show (length line)
hat Typ, String
wohingegen die do-Notation erfordert, dass einer Aktion ( getLine
vom Typ IO String
) eine andere Aktion (z . B. putStrLn (show (length line))
vom Typ IO ()
) folgt .
BEARBEITEN 2
Jörg W. Mittag definiert referentielle Transparenz allgemeiner als ich (ich habe seine Antwort positiv bewertet). Ich habe eine eingeschränkte Definition verwendet, da sich das Beispiel in der Frage auf den Rückgabewert von Funktionen konzentriert und ich diesen Aspekt veranschaulichen wollte. RT bezieht sich jedoch im Allgemeinen auf die Bedeutung des gesamten Programms, einschließlich Änderungen des globalen Status und Interaktionen mit der Umgebung (IO), die durch die Auswertung eines Ausdrucks verursacht werden. Um eine korrekte, allgemeine Definition zu erhalten, sollten Sie sich auf diese Antwort beziehen.