Ich muss zugeben, dass ich nicht viel über funktionale Programmierung weiß. Ich habe von hier und da darüber gelesen und festgestellt, dass bei der funktionalen Programmierung eine Funktion dieselbe Ausgabe für dieselbe Eingabe zurückgibt, unabhängig davon, wie oft die Funktion aufgerufen wird. Es ist genau wie eine mathematische Funktion, die dieselbe Ausgabe für denselben Wert der Eingabeparameter auswertet, die im Funktionsausdruck enthalten sind.
Betrachten Sie zum Beispiel Folgendes:
f(x,y) = x*x + y; // It is a mathematical function
Egal wie oft Sie verwenden f(10,4)
, sein Wert wird immer sein 104
. Wo immer Sie geschrieben haben f(10,4)
, können Sie es durch ersetzen 104
, ohne den Wert des gesamten Ausdrucks zu ändern. Diese Eigenschaft wird als referenzielle Transparenz eines Ausdrucks bezeichnet.
Wie Wikipedia sagt ( Link ),
Umgekehrt hängt im Funktionscode der Ausgabewert einer Funktion nur von den Argumenten ab, die in die Funktion eingegeben werden. Wenn Sie also eine Funktion f zweimal mit demselben Wert für ein Argument x aufrufen, erhalten Sie beide Male dasselbe Ergebnis f (x).
Kann eine Zeitfunktion (die die aktuelle Zeit zurückgibt ) in der Funktionsprogrammierung vorhanden sein?
Wenn ja, wie kann es dann existieren? Verstößt es nicht gegen das Prinzip der funktionalen Programmierung? Es verletzt insbesondere die referenzielle Transparenz, die eine der Eigenschaften der funktionalen Programmierung ist (wenn ich sie richtig verstehe).
Oder wenn nein, wie kann man dann die aktuelle Zeit in der funktionalen Programmierung kennen?
Antworten:
Eine andere Möglichkeit, dies zu erklären, ist folgende: Keine Funktion kann die aktuelle Zeit abrufen (da sie sich ständig ändert), aber eine Aktion kann die aktuelle Zeit abrufen.
getClockTime
Angenommen , dies ist eine Konstante (oder eine Nullfunktion, wenn Sie möchten), die die Aktion zum Abrufen der aktuellen Zeit darstellt. Diese Aktion ist jedes Mal dieselbe, unabhängig davon, wann sie verwendet wird. Sie ist also eine echte Konstante.Angenommen, es
print
handelt sich um eine Funktion, die einige Zeit in Anspruch nimmt und auf der Konsole gedruckt wird. Da Funktionsaufrufe in einer reinen Funktionssprache keine Nebenwirkungen haben können, stellen wir uns stattdessen vor, dass es sich um eine Funktion handelt, die einen Zeitstempel verwendet und die Aktion zum Drucken an die Konsole zurückgibt . Auch dies ist eine reelle Funktion, denn wenn man ihm die gleichen Zeitstempel gibt, wird es die gleiche Rück Aktion des Druckens jedes Mal.Wie können Sie nun die aktuelle Uhrzeit auf der Konsole drucken? Nun, Sie müssen die beiden Aktionen kombinieren. Wie können wir das machen? Wir können nicht nur passieren
getClockTime
zuprint
, da Druck , der einen Zeitstempel erwartet, keine Aktion. Wir können uns jedoch vorstellen, dass es einen Operator gibt>>=
, der zwei Aktionen kombiniert , eine, die einen Zeitstempel erhält, und eine, die eine als Argument verwendet und diese druckt. Wenn Sie dies auf die zuvor erwähnten Aktionen anwenden, ist das Ergebnis ... tadaaa ... eine neue Aktion, die die aktuelle Zeit abruft und sie druckt. Und genau so wird es übrigens in Haskell gemacht.Konzeptionell können Sie es also folgendermaßen anzeigen: Ein reines Funktionsprogramm führt keine E / A aus, sondern definiert eine Aktion , die das Laufzeitsystem dann ausführt. Die Aktion ist jedes Mal dieselbe, aber das Ergebnis ihrer Ausführung hängt von den Umständen ab, unter denen sie ausgeführt wird.
Ich weiß nicht, ob dies klarer war als die anderen Erklärungen, aber es hilft mir manchmal, es so zu sehen.
quelle
getClockTime
eine Aktion anstelle einer Funktion aufgerufen . Nun, wenn Sie so nennen, rufen Sie dann jede Funktion Aktion , dann auch zwingend notwendig , Programmierung würde funktionelles programmming. Oder vielleicht möchten Sie es Aktionsprogrammierung nennen .main
Aktion zu integrieren. Dadurch kann reiner Funktionscode vom imperativen Code getrennt werden, und diese Trennung wird vom Typsystem erzwungen. Wenn Sie Aktionen als erstklassige Objekte behandeln, können Sie sie auch weitergeben und Ihre eigenen "Kontrollstrukturen" erstellen.->
- so definiert der Standard den Begriff und das ist wirklich die einzig sinnvolle Definition im Kontext von Haskell. Also ist etwas, dessen TypIO Whatever
ist, keine Funktion.putStrLn
ist keine Aktion - es ist eine Funktion, die eine Aktion zurückgibt .getLine
ist eine Variable, die eine Aktion enthält . Aktionen sind die Werte, Variablen und Funktionen sind die "Container" / "Labels", die wir diesen Aktionen geben.Ja und nein.
Verschiedene funktionale Programmiersprachen lösen sie unterschiedlich.
In Haskell (einem sehr reinen) muss all dieses Zeug in einer sogenannten I / O-Monade passieren - siehe hier .
Sie können sich vorstellen, dass Sie eine andere Eingabe (und Ausgabe) in Ihre Funktion (den Weltzustand) einbringen oder einfacher als einen Ort, an dem "Unreinheit" wie das Erhalten der sich ändernden Zeit auftritt.
Andere Sprachen wie F # haben nur eine gewisse Unreinheit eingebaut, sodass Sie eine Funktion haben können, die unterschiedliche Werte für dieselbe Eingabe zurückgibt - genau wie normale imperative Sprachen.
Wie Jeffrey Burka in seinem Kommentar erwähnte: Hier ist die schöne Einführung in die I / O-Monade direkt aus dem Haskell-Wiki.
quelle
In Haskell verwendet man ein Konstrukt namens Monade , um Nebenwirkungen zu behandeln. Eine Monade bedeutet im Grunde, dass Sie Werte in einen Container einkapseln und einige Funktionen haben, um Funktionen von Werten zu Werten innerhalb eines Containers zu verketten. Wenn unser Container den Typ hat:
Wir können E / A-Aktionen sicher implementieren. Dieser Typ bedeutet: Eine Aktion vom Typ
IO
ist eine Funktion, die ein Token vom Typ nimmtRealWorld
und ein neues Token zusammen mit einem Ergebnis zurückgibt.Die Idee dahinter ist, dass jede E / A-Aktion den äußeren Zustand mutiert, der durch das magische Zeichen dargestellt wird
RealWorld
. Mit Monaden kann man mehrere Funktionen verketten, die die reale Welt miteinander mutieren. Die wichtigste Funktion einer Monade ist die>>=
ausgeprägte Bindung :>>=
führt eine Aktion und eine Funktion aus, die das Ergebnis dieser Aktion aufnimmt und daraus eine neue Aktion erstellt. Der Rückgabetyp ist die neue Aktion. Nehmen wir zum Beispiel an, es gibt eine Funktionnow :: IO String
, die einen String zurückgibt, der die aktuelle Zeit darstellt. Wir können es mit der FunktionputStrLn
zum Ausdrucken verketten:Oder geschrieben in
do
-Notation, die einem imperativen Programmierer besser bekannt ist:All dies ist rein, da wir die Mutation und Informationen über die Welt außerhalb dem
RealWorld
Token zuordnen. Jedes Mal, wenn Sie diese Aktion ausführen, erhalten Sie natürlich eine andere Ausgabe, aber die Eingabe ist nicht dieselbe: DasRealWorld
Token ist unterschiedlich.quelle
RealWorld
Rauchschutz unzufrieden . Das Wichtigste ist jedoch, wie dieses angebliche Objekt in einer Kette weitergegeben wird. Das fehlende Teil ist dort, wo es beginnt, wo sich die Quelle oder Verbindung zur realen Welt befindet - es beginnt mit der Hauptfunktion, die in der E / A-Monade ausgeführt wird.RealWorld
Objekt vorstellen, das beim Start an das Programm übergeben wird.main
Funktion einRealWorld
Argument. Erst nach der Hinrichtung wird es übergeben.RealWorld
und nur mickrige Funktionen bereitstellen, um es zu ändernputStrLn
, ist, dass einige Haskell-Programmierer sich nichtRealWorld
mit einem ihrer Programme ändern , so dass die Adresse und das Geburtsdatum von Haskell Curry so sind, dass sie Nachbarn von nebenan werden Aufwachsen (dies könnte das Zeit-Raum-Kontinuum derart beschädigen, dass die Programmiersprache Haskell verletzt wird.)RealWorld -> (a, RealWorld)
zerfällt auch bei gleichzeitiger Verwendung nicht als Metapher, solange Sie bedenken, dass die reale Welt jederzeit von anderen Teilen des Universums außerhalb Ihrer Funktion (oder Ihres aktuellen Prozesses) verändert werden kann. So (a) das methaphor nicht brechen, und (b) jedes Mal , wenn ein Wert, der hatRealWorld
als seine Art an eine Funktion übergeben wird, hat die Funktion neu bewertet werden, weil die reale Welt wird in der Zwischenzeit geändert hat ( Dies wird als @fuz erklärt modelliert und gibt jedes Mal, wenn wir mit der realen Welt interagieren, einen anderen 'Token-Wert' zurück.Die meisten funktionalen Programmiersprachen sind nicht rein, dh sie ermöglichen, dass Funktionen nicht nur von ihren Werten abhängen. In diesen Sprachen ist es durchaus möglich, dass eine Funktion die aktuelle Zeit zurückgibt. Von den Sprachen, mit denen Sie diese Frage markiert haben, gilt dies für Scala und F # (sowie die meisten anderen Varianten von ML ).
In Sprachen wie Haskell und Clean , die rein sind, ist die Situation anders. In Haskell wäre die aktuelle Zeit nicht über eine Funktion verfügbar, sondern über eine sogenannte E / A-Aktion, mit der Haskell Nebenwirkungen einkapselt.
In Clean wäre es eine Funktion, aber die Funktion würde einen Weltwert als Argument nehmen und als Ergebnis einen neuen Weltwert (zusätzlich zur aktuellen Zeit) zurückgeben. Das Typsystem würde sicherstellen, dass jeder Weltwert nur einmal verwendet werden kann (und jede Funktion, die einen Weltwert verbraucht, würde einen neuen erzeugen). Auf diese Weise müsste die Zeitfunktion jedes Mal mit einem anderen Argument aufgerufen werden und könnte daher jedes Mal eine andere Zeit zurückgeben.
quelle
"Aktuelle Zeit" ist keine Funktion. Es ist ein Parameter. Wenn Ihr Code von der aktuellen Zeit abhängt, bedeutet dies, dass Ihr Code nach Zeit parametriert ist.
quelle
Dies kann absolut rein funktional erfolgen. Es gibt verschiedene Möglichkeiten, dies zu tun. Am einfachsten ist es jedoch, wenn die Zeitfunktion nicht nur die Zeit, sondern auch die Funktion zurückgibt, die Sie aufrufen müssen, um die nächste Zeitmessung zu erhalten .
In C # können Sie es folgendermaßen implementieren:
(Beachten Sie, dass dies ein Beispiel ist, das einfach und nicht praktisch sein soll. Insbesondere können die Listenknoten nicht durch Müll gesammelt werden, da sie von ProgramStartTime gerootet werden.)
Diese 'ClockStamp'-Klasse verhält sich wie eine unveränderliche verknüpfte Liste, aber die Knoten werden tatsächlich bei Bedarf generiert, damit sie die' aktuelle 'Zeit enthalten können. Jede Funktion, die die Zeit messen möchte, sollte einen 'clockStamp'-Parameter haben und auch ihre letzte Zeitmessung im Ergebnis zurückgeben (damit der Aufrufer keine alten Messungen sieht), wie folgt:
Natürlich ist es etwas unpraktisch, diese letzte Messung rein und raus, rein und raus, rein und raus zu lassen. Es gibt viele Möglichkeiten, das Boilerplate auszublenden, insbesondere auf der Ebene des Sprachdesigns. Ich denke, Haskell benutzt diese Art von Trick und versteckt dann die hässlichen Teile mit Monaden.
quelle
i++
in der for-Schleife ist nicht referenziell transparent;)struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }
. Aber Code mit diesem würde immer noch nicht so gut aussehen wie Haskell mitdo
Syntax.SelectMany
, die die Syntax des Abfrageverständnisses aktiviert . Sie können jedoch immer noch nicht polymorph über Monaden programmieren, so dass es ein harter Kampf gegen das schwache Typsystem ist :(Ich bin überrascht, dass in keiner der Antworten oder Kommentare Kohlegebren oder Koinduktion erwähnt werden. Normalerweise wird die Koinduktion erwähnt, wenn über unendliche Datenstrukturen nachgedacht wird, sie ist jedoch auch auf einen endlosen Strom von Beobachtungen anwendbar, beispielsweise auf ein Zeitregister auf einer CPU. Eine Kohlegebra modelliert den verborgenen Zustand; und Koinduktionsmodelle, die diesen Zustand beobachten . (Normale Induktionsmodelle, die den Zustand konstruieren .)
Dies ist ein heißes Thema in der reaktiven funktionalen Programmierung. Wenn Sie an solchen Dingen interessiert sind, lesen Sie diese: http://digitalcommons.ohsu.edu/csetech/91/ (28 Seiten)
quelle
Ja, es ist möglich, dass eine reine Funktion die Zeit zurückgibt, wenn diese Zeit als Parameter angegeben wird. Unterschiedliches Zeitargument, unterschiedliches Zeitergebnis. Bilden Sie dann auch andere Funktionen der Zeit und kombinieren Sie sie mit einem einfachen Vokabular von Funktionen (-of-time) -transformierenden Funktionen (höherer Ordnung). Da der Ansatz zustandslos ist, kann die Zeit hier eher kontinuierlich (auflösungsunabhängig) als diskret sein, was die Modularität erheblich erhöht . Diese Intuition ist die Grundlage der Functional Reactive Programming (FRP).
quelle
Ja! Du hast Recht! Now () oder CurrentTime () oder eine Methodensignatur eines solchen Geschmacks zeigt in keiner Weise referenzielle Transparenz. Durch Anweisung an den Compiler wird es jedoch durch einen Systemtakteingang parametriert.
Bei der Ausgabe sieht Now () möglicherweise so aus, als würde die referenzielle Transparenz nicht eingehalten. Das tatsächliche Verhalten der Systemuhr und der darüber liegenden Funktion hängt jedoch von der referenziellen Transparenz ab.
quelle
Ja, eine Funktion zum Abrufen der Zeit kann in der funktionalen Programmierung unter Verwendung einer leicht modifizierten Version der funktionalen Programmierung existieren, die als unreine funktionale Programmierung bekannt ist (die Standard- oder die Hauptfunktion ist die reine funktionale Programmierung).
Wenn Sie Zeit haben (oder Dateien lesen oder Raketen starten), muss der Code mit der Außenwelt interagieren, um die Arbeit zu erledigen, und diese Außenwelt basiert nicht auf den reinen Grundlagen der funktionalen Programmierung. Um einer reinen funktionalen Programmierwelt die Interaktion mit dieser unreinen Außenwelt zu ermöglichen, haben die Menschen unreine funktionale Programmierung eingeführt. Schließlich ist Software, die nicht mit der Außenwelt interagiert, nichts anderes als einige mathematische Berechnungen.
Nur wenige funktionale Programmiersprachen verfügen über diese integrierte Verunreinigungsfunktion, sodass es nicht einfach ist, herauszufinden, welcher Code unrein und welcher rein ist (wie F # usw.), und einige funktionale Programmiersprachen stellen sicher, dass Sie bei unreinen Dingen sicherstellen Dieser Code ist im Vergleich zu reinem Code wie Haskell deutlich hervorzuheben.
Eine andere interessante Möglichkeit, dies zu sehen, wäre, dass Ihre Funktion zum Abrufen von Zeit in der funktionalen Programmierung ein "Welt" -Objekt benötigt, das den aktuellen Zustand der Welt wie Zeit, Anzahl der auf der Welt lebenden Menschen usw. aufweist. Dann wird Zeit von welcher Welt abgerufen Objekt wäre immer rein, dh Sie passieren den gleichen Weltzustand, Sie werden immer die gleiche Zeit bekommen.
quelle
Ihre Frage verbindet zwei verwandte Maße einer Computersprache: funktional / imperativ und rein / unrein.
Eine funktionale Sprache definiert Beziehungen zwischen Ein- und Ausgaben von Funktionen, und eine zwingende Sprache beschreibt bestimmte Operationen in einer bestimmten Reihenfolge, die ausgeführt werden soll.
Eine reine Sprache erzeugt oder hängt nicht von Nebenwirkungen ab, und eine unreine Sprache verwendet sie durchgehend.
Hundertprozentig reine Programme sind grundsätzlich nutzlos. Sie führen möglicherweise eine interessante Berechnung durch, aber da sie keine Nebenwirkungen haben können, haben sie keine Eingabe oder Ausgabe, sodass Sie nie wissen würden, was sie berechnet haben.
Um überhaupt nützlich zu sein, muss ein Programm mindestens ein bisschen unrein sein. Eine Möglichkeit, ein reines Programm nützlich zu machen, besteht darin, es in eine dünne unreine Hülle zu legen. Wie dieses ungetestete Haskell-Programm:
quelle
IO
Werte und Ergebnisse für rein halten.Sie sprechen ein sehr wichtiges Thema in der funktionalen Programmierung an, dh das Durchführen von E / A. Viele reine Sprachen verwenden eingebettete domänenspezifische Sprachen, z. B. eine Subsprache, deren Aufgabe es ist, Aktionen zu codieren , die Ergebnisse haben können.
Die Haskell-Laufzeit erwartet beispielsweise, dass ich eine Aktion mit dem Namen definiere
main
, die sich aus allen Aktionen zusammensetzt, aus denen mein Programm besteht. Die Laufzeit führt dann diese Aktion aus. Meistens wird dabei reiner Code ausgeführt. Von Zeit zu Zeit verwendet die Laufzeit die berechneten Daten, um E / A durchzuführen, und gibt Daten in reinen Code zurück.Sie könnten sich beschweren, dass dies nach Betrug klingt, und in gewisser Weise: Indem Sie Aktionen definieren und erwarten, dass die Laufzeit sie ausführt, kann der Programmierer alles tun, was ein normales Programm tun kann. Das starke Typsystem von Haskell schafft jedoch eine starke Barriere zwischen reinen und "unreinen" Teilen des Programms: Sie können nicht einfach zwei Sekunden zur aktuellen CPU-Zeit hinzufügen und diese drucken. Sie müssen eine Aktion definieren, die zum aktuellen führt CPU-Zeit und geben Sie das Ergebnis an eine andere Aktion weiter, die zwei Sekunden hinzufügt und das Ergebnis druckt. Zu viel von einem Programm zu schreiben wird jedoch als schlechter Stil angesehen, da es schwierig ist, zu schließen, welche Effekte verursacht werden, im Vergleich zu Haskell-Typen, die uns alles erzählen wir über einen Wert wissen können.
Beispiel:
clock_t c = time(NULL); printf("%d\n", c + 2);
in C vs.main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)
in Haskell. Der Operator>>=
wird zum Erstellen von Aktionen verwendet, wobei das Ergebnis der ersten an eine Funktion übergeben wird, die zur zweiten Aktion führt. Diese Haskell-Compiler sehen ziemlich arkan aus und unterstützen syntaktischen Zucker, mit dem wir den letzteren Code wie folgt schreiben können:Letzteres sieht ziemlich zwingend aus, nicht wahr?
quelle
Es existiert nicht in einem rein funktionalen Sinne.
Es kann zunächst hilfreich sein zu wissen, wie eine Zeit auf einem Computer abgerufen wird. Im Wesentlichen gibt es eine integrierte Schaltung, die die Zeit verfolgt (was der Grund ist, warum ein Computer normalerweise eine kleine Zellenbatterie benötigt). Dann könnte es einen internen Prozess geben, der den Wert der Zeit in einem bestimmten Speicherregister festlegt. Was im Wesentlichen auf einen Wert hinausläuft, der von der CPU abgerufen werden kann.
Für Haskell gibt es ein Konzept einer "E / A-Aktion", die einen Typ darstellt, der zur Ausführung eines E / A-Prozesses erstellt werden kann. Anstatt auf einen
time
Wert zu verweisen, verweisen wir auf einenIO Time
Wert. All dies wäre rein funktional. Wir beziehen uns nicht auftime
etwas im Sinne von "Lesen Sie den Wert des Zeitregisters" .Wenn wir das Haskell-Programm tatsächlich ausführen, würde die E / A-Aktion tatsächlich stattfinden.
quelle