In FP-Sprachen liefert das wiederholte Aufrufen einer Funktion mit denselben Parametern immer wieder dasselbe Ergebnis (dh referenzielle Transparenz).
Aber eine Funktion wie diese (Pseudocode):
function f(a, b) {
return a + b + currentDateTime.seconds;
}
wird nicht dasselbe Ergebnis für dieselben Parameter zurückgeben.
Wie werden diese Fälle in FP behandelt?
Wie wird referenzielle Transparenz durchgesetzt? Oder ist es nicht und es hängt von den Programmierern ab, sich zu verhalten?
currentDateTime
ausf
, in Sprachen , die referentielle Transparenz (wie Haskell) erzwingen. Ich werde jemand anderem eine detailliertere Antwort geben lassen :) (Hinweis:currentDateTime
Hat IO, und dies wird in seiner ArtAntworten:
a
undb
sindNumber
s, währendcurrentDateTime.seconds
einIO<Number>
. Diese Typen sind nicht kompatibel. Sie können sie nicht addieren. Daher ist Ihre Funktion nicht gut typisiert und wird einfach nicht kompiliert. Zumindest geschieht dies in reinen Sprachen mit einem statischen Typsystem wie Haskell. In unreinen Sprachen wie ML, Scala oder F # ist es Sache des Programmierers, die referenzielle Transparenz sicherzustellen, und natürlich gibt es in dynamisch typisierten Sprachen wie Clojure oder Scheme kein statisches Typsystem, um referenzielle Transparenz zu erzwingen.quelle
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:
Wo ist also referentielle Transparenz?
f
ist eine Funktion, die bei zwei Ganzzahlena
undb
eine Aktion erstellt, wie Sie am Rückgabetyp erkennen könnenIO 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 Ausdrucks4 + 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 TypIO ()
.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:Die
do
Notation gibt an, dass die von der Hauptfunktion zurückgegebene Aktion durch eine sequentielle Zusammensetzung von zwei kleineren Aktionen erhalten wird, und diex <-
Notation gibt an, dass der in der ersten Aktion erzeugte Wert an die zweite Aktion übergeben werden muss.In der zweiten Aktion
Der Name
x
ist an die Ganzzahl gebunden, die durch Ausführen der Aktion erzeugt wirdEin 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:In diesem Fall gibt es nur eine Maßnahme, nämlich
putStrLn (show y)
, undy
ist mit dem Ergebnis der Anwendung der reinen Funktion gebunden+
. Wir könnten diese Hauptaktion auch wie folgt definieren:Beachten Sie also die unterschiedliche Syntax
Zusammenfassung
quelle
executing actions is NOT function application
Satz ein wenig zu beschreiben ? In meinem Beispiel wollte ich eine ganze Zahl zurückgeben. Was passiert, wenn eine Ganzzahl zurückgegeben wird?f
können Sie keinen Typ habenIO Integer
(das ist eine Aktion, keine Ganzzahl). Dann kann es jedoch nicht das "aktuelle Datum" aufrufen, das einen Typ hatIO Integer
.Show
verfügbar haben. Sie können jedoch problemlos eine hinzufügen. In diesem Fall wird der Code kompiliert und funktioniert einwandfrei.IO
Handlungen in Bezug auf sind nichts Besonderesshow
.Der übliche Ansatz besteht darin, dem Compiler zu ermöglichen, zu verfolgen, ob eine Funktion im gesamten Aufrufdiagramm rein ist oder nicht, und Code abzulehnen, der Funktionen als rein deklariert, die unreine Dinge tun (wobei "Aufrufen einer unreinen Funktion" auch eine unreine Sache ist).
Haskell tut dies, indem er alles in der Sprache selbst rein macht ; Alles Unreine läuft zur Laufzeit, nicht die Sprache selbst. Die Sprache erstellt lediglich E / A-Aktionen mit reinen Funktionen. Die Laufzeit findet dann die reine Funktion, die
main
vom angegebenenMain
Modul aufgerufen wird, wertet sie aus und führt die resultierende (unreine) Aktion aus.Andere Sprachen sind pragmatischer; Ein üblicher Ansatz besteht darin, eine Syntax zum Markieren von Funktionen als "rein" hinzuzufügen und unreine Aktionen (Variablenaktualisierungen, Aufrufen unreiner Funktionen, E / A-Konstrukte) in solchen Funktionen zu verbieten.
In Ihrem Beispiel
currentDateTime
handelt es sich um eine unreine Funktion (oder etwas, das sich wie eine verhält). Daher ist das Aufrufen innerhalb eines reinen Blocks verboten und würde einen Compilerfehler verursachen. In Haskell würde Ihre Funktion ungefähr so aussehen:Wenn Sie dies in einer Nicht-E / A-Funktion wie folgt versucht haben:
... dann würde der Compiler Ihnen sagen, dass Ihre Typen nicht auschecken -
getCurrentTime
ist vom TypIO Time
, nichtTime
, abertimeSeconds
erwartetTime
. Mit anderen Worten, Haskell nutzt sein Typsystem, um die Reinheit zu modellieren (und durchzusetzen).quelle