it-swarm.com.de

Wie kann eine Zeitfunktion in der funktionalen Programmierung existieren?

Ich muss zugeben, dass ich nicht viel über funktionale Programmierung weiß. Ich habe hier und da darüber gelesen und dabei festgestellt, dass eine Funktion bei der funktionalen Programmierung 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 für denselben Wert der Eingabeparameter, die in den Funktionsausdruck einbezogen sind, dieselbe Ausgabe ergibt.

Betrachten Sie zum Beispiel Folgendes:

f(x,y) = x*x + y; // It is a mathematical function

Egal wie oft Sie f(10,4) verwenden, sein Wert ist immer 104. Daher können Sie f(10,4) überall dort, wo Sie geschrieben haben, durch 104 Ersetzen, ohne den Wert des gesamten Ausdrucks zu ändern. Diese Eigenschaft wird als referentielle Transparenz eines Ausdrucks bezeichnet.

Wie Wikipedia sagt ( link ),

Umgekehrt hängt der Ausgabewert einer Funktion im Funktionscode 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 dasselbe Ergebnis f(x) beide Male.

Kann es in der Funktionsprogrammierung eine Zeitfunktion geben, die die aktuelle Zeit zurückgibt?

  • Wenn ja, wie kann es dann existieren? Verstößt es nicht gegen das Prinzip der funktionalen Programmierung? Es verletzt insbesondere referentielle Transparenz , was eine der Eigenschaften der funktionalen Programmierung ist (wenn ich es richtig verstehe).

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

625
Nawaz

Eine andere Möglichkeit, dies zu erklären, ist folgende: no Funktion kann die aktuelle Zeit abrufen (da sie sich ständig ändert), aber ein Aktion kann die aktuelle Zeit abrufen. Angenommen, getClockTime ist eine Konstante (oder eine Nullfunktion, wenn Sie möchten), die das Aktion zum Abrufen der aktuellen Zeit darstellt. Dies Aktion ist jedes Mal gleich, egal wann es verwendet wird, es ist also eine echte Konstante.

Angenommen, print ist eine Funktion, die eine gewisse Zeit benötigt, um auf der Konsole ausgegeben zu werden. 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 benötigt und die Aktion des Druckens an die Konsole zurückgibt. Auch dies ist eine echte Funktion, denn wenn Sie denselben Zeitstempel eingeben, wird jedes Mal dasselbe Aktion zum Drucken zurückgegeben.

Wie können Sie nun die aktuelle Uhrzeit auf der Konsole ausgeben? Nun, Sie müssen die beiden Aktionen kombinieren. Wie können wir das machen? Wir können getClockTime nicht einfach an print übergeben, da print einen Zeitstempel und keine Aktion erwartet. Aber wir können uns vorstellen, dass es einen Operator gibt, >>=, was kombiniert zwei Aktionen, eine, die einen Zeitstempel erhält, und eine, die einen als Argument verwendet und ihn ausgibt. Wenn Sie dies auf die zuvor genannten Aktionen anwenden, ist das Ergebnis ... tadaaa ... eine neue Aktion, die die aktuelle Zeit abruft und diese druckt. Und das ist übrigens genau so, wie es in Haskell gemacht wird.

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

Sie können es also konzeptionell folgendermaßen anzeigen: Ein reines Funktionsprogramm führt keine E/A aus, es definiert ein Aktion, das das Laufzeitsystem dann ausführt. Die Aktion ist jedes Mal dieselbe, aber das Ergebnis der 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.

160
dainichi

Ja und nein.

Unterschiedliche funktionale Programmiersprachen lösen sie unterschiedlich.

In Haskell (einem sehr reinen) muss all dieses Zeug in etwas passieren, das I/O-Monade - siehe hier .

Sie können sich vorstellen, wie Sie einen weiteren Input (und Output) in Ihre Funktion (den Weltzustand) einbringen, oder wie Sie es sich einfacher als einen Ort vorstellen, an dem "Unreinheit" wie das Einsetzen der sich ändernden Zeit geschieht.

In anderen Sprachen wie F # ist nur eine gewisse Unreinheit eingebaut, sodass Sie eine Funktion haben können, die unterschiedliche Werte für dieselbe Eingabe zurückgibt - genau wie in normalen imperativen Sprachen.

Wie Jeffrey Burka in seinem Kommentar erwähnte: Hier ist die nette Einführung in die I/O-Monade direkt aus dem Haskell-Wiki.

350
Carsten

In Haskell verwendet man ein Konstrukt namens Monade, um mit Nebenwirkungen umzugehen. 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 sicher IO Aktionen implementieren. Dieser Typ bedeutet: Eine Aktion vom Typ IO ist eine Funktion, die ein Token vom Typ RealWorld annimmt und ein neues zurückgibt Token zusammen mit einem Ergebnis.

Die Idee dahinter ist, dass jede IO Aktion den äußeren Zustand mutiert, der durch das magische Zeichen RealWorld dargestellt wird. Mit Monaden kann man mehrere Funktionen verketten, die die reale Welt mutieren. Die wichtigste Funktion einer Monade ist >>=, ausgesprochen bind:

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

>>= führt eine Aktion und eine Funktion aus, die das Ergebnis dieser Aktion übernimmt und daraus eine neue Aktion erstellt. Der Rückgabetyp ist die neue Aktion. Stellen wir uns zum Beispiel vor, es gäbe eine Funktion now :: IO String, der einen String zurückgibt, der die aktuelle Zeit darstellt. Wir können es mit der Funktion putStrLn verketten, um es auszudrucken:

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 Information über die Welt außerhalb des Tokens RealWorld zuordnen. Jedes Mal, wenn Sie diese Aktion ausführen, erhalten Sie natürlich eine andere Ausgabe, aber die Eingabe ist nicht dieselbe: Das Token RealWorld ist unterschiedlich.

144
fuz

Die meisten funktionalen Programmiersprachen sind nicht rein, d. H. Sie ermöglichen, dass Funktionen nicht nur von ihren Werten abhängen. In diesen Sprachen ist es durchaus möglich, eine Funktion zu haben, die die aktuelle Uhrzeit zurückgibt. Von den Sprachen, mit denen Sie diese Frage getaggt 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 IO) - 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 Typensystem 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.

70
sepp2k

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

49
Vlad Patryshev

Es kann absolut rein funktional erfolgen. Es gibt verschiedene Möglichkeiten, aber die einfachste besteht darin, dass die Zeitfunktion nicht nur die Zeit zurückgibt, sondern auch die Funktion, die Sie aufrufen müssen, um die nächste Zeitmessung durchzuführen.

In C # könnten Sie es so 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 einfaches und nicht praktikables Beispiel ist. Insbesondere können die Listenknoten nicht mit Datenmüll gesammelt werden, da sie von ProgramStartTime als Root erstellt wurden.)

Diese 'ClockStamp'-Klasse verhält sich wie eine unveränderliche verknüpfte Liste, aber die Knoten werden tatsächlich bei Bedarf generiert, sodass sie die' aktuelle 'Zeit enthalten können. Jede Funktion, die die Zeit messen möchte, sollte einen 'clockStamp'-Parameter haben und auch die letzte Zeitmessung in ihrem 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 ein bisschen unpraktisch, diese letzte Messung ein- und auszuschalten, ein- und auszuschalten, ein- und auszuschalten. Es gibt viele Möglichkeiten, das Boilerplate auszublenden, insbesondere auf der Ebene des Sprachdesigns. Ich denke, Haskell verwendet diese Art von Trick und versteckt dann die hässlichen Teile mit Monaden.

21
Craig Gidney

Ich bin überrascht, dass keine der Antworten oder Kommentare Kohlegebren oder Coinduktionen erwähnen. Normalerweise wird die Koinduktion bei Überlegungen zu unendlichen Datenstrukturen erwähnt, sie ist jedoch auch auf einen endlosen Strom von Beobachtungen anwendbar, beispielsweise auf ein Zeitregister in einer CPU. Eine Kohlegebra modelliert den verborgenen Zustand. und Coinduktionsmodelle , die diesen Zustand beobachten . (Normale Induktionsmodelle , die einen Zustand aufbauen.)

Dies ist ein aktuelles Thema in der reaktiven funktionalen Programmierung. Wenn Sie an solchen Dingen interessiert sind, lesen Sie diese: http://digitalcommons.ohsu.edu/csetech/91/ (28 S.)

16

Ja, eine reine Funktion kann die Zeit zurückgeben, wenn sie diese Zeit als Parameter angibt. 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 nicht diskret, sondern kontinuierlich (auflösungsunabhängig) sein Erhöhung der Modularität . Diese Intuition ist die Grundlage für Functional Reactive Programming (FRP).

12
Conal

Ja! Du hast Recht! Now () oder CurrentTime () oder eine Methodensignatur eines solchen Flavours weisen in keiner Weise referenzielle Transparenz auf. Auf Anweisung an den Compiler wird er jedoch über einen Systemtakteingang parametriert.

Bei der Ausgabe sieht Now () möglicherweise so aus, als würde es der referenziellen Transparenz nicht folgen. Das tatsächliche Verhalten der Systemuhr und der darüber liegenden Funktion ist jedoch von referentieller Transparenz abhängig.

11
MduSenthil

Ja, in der funktionalen Programmierung kann eine Zeitfunktion vorhanden sein, die eine leicht modifizierte Version der funktionalen Programmierung verwendet, die als unreine funktionale Programmierung bezeichnet wird (die Standard- oder die Hauptfunktion ist reine funktionale Programmierung).

Wenn Sie die Zeit abrufen (oder eine Datei lesen oder eine Rakete starten), muss der Code mit der Außenwelt interagieren, um die Aufgabe zu erledigen, und diese Außenwelt basiert nicht auf den reinen Grundlagen der funktionalen Programmierung. Damit eine reine funktionale Programmierwelt mit dieser unreinen Außenwelt interagieren kann, haben die Menschen eine unreine funktionale Programmierung eingeführt. Schließlich ist Software, die nicht mit der Außenwelt interagiert, nur für einige mathematische Berechnungen nützlich.

In einigen funktionalen Programmiersprachen ist diese Verunreinigungsfunktion so eingebaut, dass es nicht einfach ist, herauszufinden, welcher Code unrein und welcher rein ist (wie F # usw.), und einige funktionalen Programmiersprachen stellen sicher, dass Sie unreine Dinge tun, wenn Sie dies tun Dieser Code unterscheidet sich deutlich von reinem Code wie Haskell.

Ein anderer interessanter Weg, dies zu sehen, wäre, dass Ihre Zeitfunktion in der funktionalen Programmierung ein "Welt" -Objekt nehmen würde, das den aktuellen Zustand der Welt wie Zeit, Anzahl der Menschen, die in der Welt leben, usw. hat. Dann Zeit von welcher Welt abrufen Objekt wäre immer rein, dh du gehst in den gleichen Weltzustand, du wirst immer die gleiche Zeit bekommen.

11
Ankur

Ihre Frage verknüpft zwei verwandte Maße einer Computersprache: funktional/imperativ und rein/unrein.

Eine funktionale Sprache definiert Beziehungen zwischen Eingaben und Ausgaben von Funktionen, und eine imperative Sprache beschreibt bestimmte Operationen in einer bestimmten Reihenfolge, die auszuführen ist.

Eine reine Sprache verursacht keine Nebenwirkungen oder hängt von diesen ab, und eine unreine Sprache verwendet sie durchgehend.

Einhundertprozentig reine Programme sind grundsätzlich unbrauchbar. Sie können eine interessante Berechnung durchführen, aber da sie keine Nebenwirkungen haben, haben sie keine Eingabe oder Ausgabe, sodass Sie nie wissen, was sie berechnet haben.

Um überhaupt nützlich zu sein, muss ein Programm mindestens ein unreiner Keks sein. Eine Möglichkeit, ein reines Programm nützlich zu machen, besteht darin, es in einen dünnen, unreinen Wrapper zu legen. So wie dieses ungeprüfte 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
7
NovaDenizen

Sie sprechen ein sehr wichtiges Thema der funktionalen Programmierung an, nämlich die Durchführung von E/A. Viele reine Sprachen verwenden eingebettete domänenspezifische Sprachen, z. B. eine Subsprache, deren Aufgabe es ist, Aktionen zu codieren, was zu Ergebnissen führen kann.

Die Haskell-Laufzeit erwartet zum Beispiel, dass ich eine Aktion namens main definiere, die sich aus allen Aktionen zusammensetzt, aus denen sich mein Programm zusammensetzt. Die Laufzeit führt dann diese Aktion aus. Meistens wird dabei reiner Code ausgeführt. Von Zeit zu Zeit verwendet die Laufzeitumgebung die berechneten Daten, um E/A-Vorgänge auszuführen, und gibt Daten in reinen Code zurück.

Sie könnten sich beschweren, dass dies nach Schummeln klingt, und in gewisser Weise ist es so: Indem Sie Aktionen definieren und von der Laufzeit erwarten, dass sie ausgeführt werden, kann der Programmierer alles, was ein normales Programm tun kann. Aber das starke Typensystem von Haskell schafft eine starke Barriere zwischen reinen und "unreinen" Teilen des Programms: Sie können nicht einfach zwei Sekunden zur aktuellen CPU-Zeit hinzufügen und drucken, sondern müssen eine Aktion definieren, die zur 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 im Vergleich zu Haskell-Typen, die uns sagen, alles wir können wissen, was ein Wert ist, schwer abzuleiten ist, welche Effekte verursacht werden.

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 Betreiber >>= wird zum Erstellen von Aktionen verwendet, wobei das Ergebnis der ersten an eine Funktion übergeben wird, die zur zweiten Aktion führt. Haskell-Compiler, die recht arkane aussehen, unterstützen syntaktischen Zucker, mit dem wir den letztgenannten 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 doch ganz zwingend aus, oder?

2
MauganRa

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

Es existiert nicht in einem rein funktionalen Sinn.

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

Es kann zunächst hilfreich sein, zu wissen, wie eine Uhrzeit auf einem Computer abgerufen wird. Im Wesentlichen gibt es integrierte Schaltkreise, die die Zeit überwachen (weshalb ein Computer normalerweise eine kleine Batterie 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, mit dem ein bestimmter IO -Prozess ausgeführt werden kann. Anstatt also auf einen time Wert zu verweisen, verweisen wir auf einen IO Time Wert. All dies wäre rein funktional. Wir verweisen nicht auf time, sondern auf etwas in der Art von 'den Wert des Zeitregisters lesen'.

Wenn wir das Haskell-Programm tatsächlich ausführen, würde die Aktion IO tatsächlich stattfinden.

1