Wie kann eine Zeitfunktion in der funktionalen Programmierung existieren?


646

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?


15
Ich denke, die meisten (oder alle) funktionalen Sprachen sind nicht so streng und kombinieren funktionale und zwingende Programmierung. Zumindest ist dies mein Eindruck von F #.
Alex F

13
@Adam: Wie würde der Anrufer überhaupt die aktuelle Uhrzeit kennen?
Nawaz

29
@Adam: Eigentlich ist es in rein funktionalen Sprachen illegal (wie in: unmöglich).
sepp2k

47
@ Adam: So ziemlich. Eine reine Allzwecksprache bietet normalerweise eine Möglichkeit, den "Weltzustand" zu erreichen (dh Dinge wie die aktuelle Zeit, Dateien in einem Verzeichnis usw.), ohne die referenzielle Transparenz zu beeinträchtigen. In Haskell ist das die IO-Monade und in Clean der Welttyp. In diesen Sprachen würde eine Funktion, die die aktuelle Zeit benötigt, diese entweder als Argument verwenden oder eine E / A-Aktion anstelle ihres tatsächlichen Ergebnisses (Haskell) zurückgeben oder den Weltzustand als Argument (Clean) verwenden.
sepp2k

12
Wenn man an FP denkt, vergisst man leicht: Ein Computer ist ein großer Teil des veränderlichen Zustands. FP ändert das nicht, es verbirgt es nur.
Daniel

Antworten:


175

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. getClockTimeAngenommen , 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 printhandelt 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 getClockTimezu print, 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.

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

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.


33
Es überzeugt mich nicht. Sie haben bequemerweise getClockTimeeine 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 .
Nawaz

92
@Nawaz: Das Wichtigste dabei ist, dass Sie eine Aktion nicht innerhalb einer Funktion ausführen können. Sie können Aktionen und Funktionen nur zu neuen Aktionen kombinieren. Die einzige Möglichkeit, eine Aktion auszuführen, besteht darin, sie in Ihre mainAktion 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.
Hammar

36
Nicht alles in Haskell ist eine Funktion - das ist völliger Unsinn. Eine Funktion ist etwas, dessen Typ a enthält ->- so definiert der Standard den Begriff und das ist wirklich die einzig sinnvolle Definition im Kontext von Haskell. Also ist etwas, dessen Typ IO Whateverist, keine Funktion.
sepp2k

9
@ sepp2k Also ist myList :: [a -> b] eine Funktion? ;)
fuz

8
@ThomasEding Ich bin sehr spät zur Party, aber ich möchte nur klarstellen: putStrLnist keine Aktion - es ist eine Funktion, die eine Aktion zurückgibt . getLineist eine Variable, die eine Aktion enthält . Aktionen sind die Werte, Variablen und Funktionen sind die "Container" / "Labels", die wir diesen Aktionen geben.
kqr

356

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.


223
Das Entscheidende an der IO-Monade in Haskell ist, dass es nicht nur ein Hack ist, um dieses Problem zu umgehen. Monaden sind eine allgemeine Lösung für das Problem der Definition einer Abfolge von Aktionen in einem bestimmten Kontext. Ein möglicher Kontext ist die reale Welt, für die wir die IO-Monade haben. Ein weiterer Kontext liegt in einer atomaren Transaktion, für die wir die STM-Monade haben. Ein weiterer Kontext ist die Implementierung eines prozeduralen Algorithmus (z. B. Knuth-Shuffle) als reine Funktion, für die wir die ST-Monade haben. Und Sie können auch Ihre eigenen Monaden definieren. Monaden sind eine Art überladbares Semikolon.
Paul Johnson

2
Ich finde es nützlich, Dinge wie das Abrufen der aktuellen Zeit nicht als "Funktionen" zu bezeichnen, sondern als "Prozeduren" (obwohl die Haskell-Lösung eine Ausnahme darstellt).
Singpolym

Aus der Haskell-Perspektive sind klassische "Prozeduren" (Dinge, die Typen wie '... -> ()' haben) etwas trivial, da eine reine Funktion mit ... -> () überhaupt nichts tun kann.
Carsten

3
Der typische Haskell-Begriff ist "Aktion".
Sebastian Redl

6
"Monaden sind eine Art überladbares Semikolon." +1
user2805751

147

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:

data IO a = IO (RealWorld -> (a,RealWorld))

Wir können E / A-Aktionen sicher implementieren. Dieser Typ bedeutet: Eine Aktion vom Typ IOist eine Funktion, die ein Token vom Typ nimmt RealWorldund 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 :

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=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 Funktion now :: IO String, die einen String zurückgibt, der die aktuelle Zeit darstellt. Wir können es mit der Funktion putStrLnzum Ausdrucken verketten:

now >>= putStrLn

Oder geschrieben in do-Notation, die einem imperativen Programmierer besser bekannt ist:

do currTime <- now
   putStrLn currTime

All dies ist rein, da wir die Mutation und Informationen über die Welt außerhalb dem RealWorldToken zuordnen. Jedes Mal, wenn Sie diese Aktion ausführen, erhalten Sie natürlich eine andere Ausgabe, aber die Eingabe ist nicht dieselbe: Das RealWorldToken ist unterschiedlich.


3
-1: Ich bin mit dem RealWorldRauchschutz 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.
u0b34a0f6ae

2
@ kaizer.se Sie können sich ein globales RealWorldObjekt vorstellen, das beim Start an das Programm übergeben wird.
Fuz

6
Grundsätzlich nimmt Ihre mainFunktion ein RealWorldArgument. Erst nach der Hinrichtung wird es übergeben.
Louis Wasserman

13
Sie sehen, der Grund, warum sie das verbergen RealWorldund nur mickrige Funktionen bereitstellen, um es zu ändern putStrLn, ist, dass einige Haskell-Programmierer sich nicht RealWorldmit 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.)
PyRulez

2
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 hat RealWorldals 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.
Qqwy

73

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.


2
Das klingt so, als ob Haskell und Clean verschiedene Dinge tun. Soweit ich weiß, machen sie dasselbe, nur dass Haskell eine schönere Syntax (?) Bietet, um dies zu erreichen.
Konrad Rudolph

27
@Konrad: Sie machen dasselbe in dem Sinne, dass beide Typsystemfunktionen verwenden, um Nebenwirkungen zu abstrahieren, aber das war es auch schon. Beachten Sie, dass es sehr gut ist, die E / A-Monade in Form eines Welttyps zu erklären, aber der Haskell-Standard definiert keinen Welttyp und es ist nicht möglich, einen Wert vom Typ Welt in Haskell zu erhalten (obwohl dies sehr gut und tatsächlich möglich ist notwendig in sauber). Darüber hinaus verfügt Haskell nicht über eine eindeutige Typisierung als Typsystemfunktion. Wenn Sie also Zugriff auf eine Welt erhalten, kann nicht sichergestellt werden, dass Sie sie auf reine Weise wie Clean verwenden.
sepp2k

51

"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.


22

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:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(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:

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

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.


Interessant, aber das i++in der for-Schleife ist nicht referenziell transparent;)
snim2

@ snim2 Ich bin nicht perfekt. : P Trösten Sie sich damit, dass die schmutzige Veränderlichkeit die referenzielle Transparenz des Ergebnisses nicht beeinträchtigt. Wenn Sie dieselbe 'letzte Messung' zweimal bestehen, erhalten Sie eine veraltete nächste Messung und geben das gleiche Ergebnis zurück.
Craig Gidney

@ Strilanc Danke dafür. Ich denke in imperativem Code, daher ist es interessant zu sehen, wie funktionale Konzepte auf diese Weise erklärt werden. Ich kann mir dann eine Sprache vorstellen, in der dies natürlich und syntaktisch sauberer ist.
WW.

Sie könnten tatsächlich auch in C # den Monadenweg gehen und so das explizite Vergehen von Zeitstempeln vermeiden. Du brauchst so etwas wie struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }. Aber Code mit diesem würde immer noch nicht so gut aussehen wie Haskell mit doSyntax.
links um den

@leftaroundabout Sie können so tun, als hätten Sie eine Monade in C #, indem Sie die Bindefunktion als aufgerufene Methode implementieren 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 :(
Sara

16

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)


3
Und wie hängt das mit dieser Frage zusammen?
Nawaz

5
Bei Ihrer Frage ging es darum, zeitabhängiges Verhalten rein funktional zu modellieren, z. B. eine Funktion, die die aktuelle Systemuhr zurückgibt. Sie können entweder etwas, das einer E / A-Monade entspricht, durch alle Funktionen und deren Abhängigkeitsbaum führen, um Zugriff auf diesen Status zu erhalten. oder Sie können den Zustand modellieren, indem Sie die Beobachtungsregeln anstelle der konstruktiven Regeln definieren. Aus diesem Grund erscheint die induktive Modellierung komplexer Zustände in der funktionalen Programmierung so unnatürlich, da der verborgene Zustand tatsächlich eine koinduktive Eigenschaft ist.
Jeffrey Aguilera

Tolle Quelle! Gibt es etwas Neueres? Die JS-Community scheint immer noch mit Stream-Datenabstraktionen zu kämpfen.
Dmitri Zaitsev

12

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).


11

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.


11

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.


1
"Schließlich ist eine Software, die nicht mit der Außenwelt interagiert, nichts anderes nützlich, als einige mathematische Berechnungen durchzuführen." Soweit ich weiß, wäre auch in diesem Fall die Eingabe in die Berechnungen im Programm fest codiert, was ebenfalls nicht sehr nützlich ist. Sobald Sie Eingabedaten aus einer Datei oder einem Terminal in Ihre mathematische Berechnung einlesen möchten, benötigen Sie unreinen Code.
Giorgio

1
@ Ankur: Das ist genau das gleiche. Wenn das Programm mit etwas anderem als nur sich selbst interagiert (z. B. der Welt sozusagen über die Tastatur), ist es immer noch unrein.
Identität

1
@ Ankur: Ja, ich denke du hast recht! Auch wenn es möglicherweise nicht sehr praktisch ist, große Eingabedaten über die Befehlszeile zu übergeben, kann dies eine reine Methode sein.
Giorgio

2
Das "Weltobjekt" einschließlich der Anzahl der auf der Welt lebenden Menschen erhöht den ausführenden Computer auf ein nahezu allwissendes Niveau. Ich denke, der Normalfall ist, dass es Dinge wie die Anzahl der Dateien auf Ihrer Festplatte und das Home-Verzeichnis des aktuellen Benutzers enthält.
Ziggystar

4
@ziggystar - das "Weltobjekt" enthält eigentlich nichts - es ist lediglich ein Proxy für den sich ändernden Zustand der Welt außerhalb des Programms. Der einzige Zweck besteht darin, den veränderlichen Zustand explizit so zu markieren, dass das Typsystem ihn identifizieren kann.
Kris Nuttycombe

7

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:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print

4
Es wäre hilfreich, wenn Sie sich mit dem spezifischen Problem der Zeitbeschaffung befassen und ein wenig erläutern könnten, inwieweit wir IOWerte und Ergebnisse für rein halten.
AndrewC

Tatsächlich erwärmen sogar 100% reine Programme die CPU, was ein Nebeneffekt ist.
Jörg W Mittag

3

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:

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

Letzteres sieht ziemlich zwingend aus, nicht wahr?


1

Wenn ja, wie kann es dann existieren? Verstößt es nicht gegen das Prinzip der funktionalen Programmierung? Dies verstößt insbesondere gegen die referenzielle Transparenz

Es existiert nicht in einem rein funktionalen Sinne.

Oder wenn nein, wie kann man dann die aktuelle Zeit in der funktionalen Programmierung kennen?

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 timeWert zu verweisen, verweisen wir auf einen IO TimeWert. All dies wäre rein funktional. Wir beziehen uns nicht auf timeetwas 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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.