Ich werde versuchen, Haskells Ansatz zu veranschaulichen (ich bin nicht sicher, ob meine Intuition zu 100% korrekt ist, da ich kein Haskell-Experte bin, Korrekturen sind willkommen).
Ihr Code kann wie folgt in Haskell geschrieben werden:
import System.CPUTime
f :: Integer -> Integer -> IO Integer
f a b = do
t <- getCPUTime
return (a + b + (div t 1000000000000))
Wo ist also referentielle Transparenz?
f
ist eine Funktion, die bei zwei Ganzzahlen a
und b
eine Aktion erstellt, wie Sie am Rückgabetyp erkennen können IO Integer
. Diese Aktion ist angesichts der beiden Ganzzahlen immer dieselbe, sodass die Funktion, die ein Paar von Ganzzahlen E / A-Aktionen zuordnet, referenziell transparent ist.
Wenn diese Aktion ausgeführt wird, hängt der von ihr erzeugte ganzzahlige Wert von der aktuellen CPU-Zeit ab: Das Ausführen von Aktionen ist KEINE Funktionsanwendung.
Zusammenfassen: In Haskell können Sie reine Funktionen verwenden, um komplexe Aktionen (Sequenzieren, Verfassen von Aktionen usw.) auf referenziell transparente Weise zu erstellen und zu kombinieren. Beachten Sie erneut, dass die obige Funktion im obigen Beispiel f
keine Ganzzahl zurückgibt: Sie gibt eine Aktion zurück.
BEARBEITEN
Einige weitere Details zur JohnDoDo-Frage.
Was bedeutet es, dass "das Ausführen von Aktionen KEINE Funktionsanwendung ist"?
Bei gegebenen Mengen T1, T2, Tn, T ist eine Funktion f eine Abbildung (Beziehung), die jedem Tupel in T1 x T2 x ... x Tn einen Wert in T zuordnet. Die Funktionsanwendung erzeugt also einen Ausgabewert bei bestimmten Eingabewerten . Mit diesem Mechanismus können Sie Ausdrücke erstellen, die zu Werten ausgewertet werden, z. B. ist der Wert 10
das Ergebnis der Auswertung des Ausdrucks 4 + 6
. Beachten Sie, dass Sie beim Zuordnen von Werten zu Werten auf diese Weise keine Eingabe / Ausgabe durchführen.
In Haskell sind Aktionen Werte spezieller Typen, die durch Auswerten von Ausdrücken mit geeigneten reinen Funktionen, die mit Aktionen arbeiten, erstellt werden können. Auf diese Weise ist ein Haskell-Programm eine zusammengesetzte Aktion, die durch Auswerten der main
Funktion erhalten wird. Diese Hauptaktion hat Typ IO ()
.
Sobald diese zusammengesetzte Aktion definiert wurde, wird ein anderer Mechanismus (keine Funktionsanwendung) verwendet, um die Aktion aufzurufen / auszuführen (siehe z . B. hier ). Die gesamte Programmausführung ist das Ergebnis des Aufrufs der Hauptaktion, die wiederum Unteraktionen aufrufen kann. Dieser Aufrufmechanismus (dessen interne Details ich nicht kenne) sorgt dafür, dass alle erforderlichen E / A-Aufrufe ausgeführt werden und möglicherweise auf das Terminal, die Festplatte, das Netzwerk usw. zugegriffen wird.
Zurück zum Beispiel. Die f
obige Funktion gibt keine Ganzzahl zurück und Sie können keine Funktion schreiben, die E / A ausführt und gleichzeitig eine Ganzzahl zurückgibt: Sie müssen eine der beiden auswählen.
Sie können die zurückgegebene Aktion f 2 3
in eine komplexere Aktion einbetten . Wenn Sie beispielsweise die durch diese Aktion erzeugte Ganzzahl drucken möchten, können Sie Folgendes schreiben:
main :: IO ()
main = do
x <- f 2 3
putStrLn (show x)
Die do
Notation gibt an, dass die von der Hauptfunktion zurückgegebene Aktion durch eine sequentielle Zusammensetzung von zwei kleineren Aktionen erhalten wird, und die x <-
Notation gibt an, dass der in der ersten Aktion erzeugte Wert an die zweite Aktion übergeben werden muss.
In der zweiten Aktion
putStrLn (show x)
Der Name x
ist an die Ganzzahl gebunden, die durch Ausführen der Aktion erzeugt wird
f 2 3
Ein wichtiger Punkt ist, dass die Ganzzahl, die beim Aufrufen der ersten Aktion erzeugt wird, nur innerhalb von E / A-Aktionen leben kann: Sie kann von einer E / A-Aktion zur nächsten übergeben werden, kann jedoch nicht als einfacher ganzzahliger Wert extrahiert werden.
Vergleichen Sie die main
obige Funktion mit dieser:
main = do
let y = 2 + 3
putStrLn (show y)
In diesem Fall gibt es nur eine Maßnahme, nämlich putStrLn (show y)
, und y
ist mit dem Ergebnis der Anwendung der reinen Funktion gebunden +
. Wir könnten diese Hauptaktion auch wie folgt definieren:
main = putStrLn "5"
Beachten Sie also die unterschiedliche Syntax
x <- f 2 3 -- Inject the value produced by an action into
-- the following IO actions.
-- The value may depend on when the action is
-- actually executed. What happens when the action is
-- executed is not known here: it may get user input,
-- access the disk, the network, the system clock, etc.
let y = 2 + 3 -- Bind y to the result of applying the pure function `+`
-- to the arguments 2 and 3.
-- The value depends only on the arguments 2 and 3.
Zusammenfassung
- In Haskell werden reine Funktionen verwendet, um die Aktionen zu erstellen, die ein Programm bilden.
- Aktionen sind Werte eines bestimmten Typs.
- Da Aktionen durch Anwenden reiner Funktionen erstellt werden, ist die Aktionskonstruktion referenziell transparent.
- Nachdem eine Aktion erstellt wurde, kann sie über einen separaten Mechanismus aufgerufen werden.