Verbieten funktionale Programmiersprachen Nebenwirkungen?

10

Laut Wikipedia, funktionale Programmiersprachen , die deklarativ sind, verbieten sie Nebenwirkungen. Deklarative Programmierung im Allgemeinen versucht, Nebenwirkungen zu minimieren oder zu beseitigen.

Laut Wikipedia hängt ein Nebeneffekt auch mit Zustandsänderungen zusammen. Funktionale Programmiersprachen eliminieren in diesem Sinne tatsächlich Nebenwirkungen, da sie keinen Zustand speichern.

Zusätzlich hat eine Nebenwirkung eine andere Definition. Nebenwirkung

hat eine beobachtbare Interaktion mit seinen aufrufenden Funktionen oder der Außenwelt neben der Rückgabe eines Wertes. Beispielsweise kann eine bestimmte Funktion eine globale oder statische Variable ändern, eines ihrer Argumente ändern, eine Ausnahme auslösen, Daten in eine Anzeige oder Datei schreiben, Daten lesen oder andere Nebenwirkungen aufrufen.

In diesem Sinne erlauben funktionale Programmiersprachen tatsächlich Nebenwirkungen, da es unzählige Beispiele für Funktionen gibt, die sich auf ihre Außenwelt auswirken, andere Funktionen aufrufen, Ausnahmen auslösen, in Dateien schreiben usw.

Erlauben funktionale Programmiersprachen Nebenwirkungen oder nicht?

Oder ich verstehe nicht, was als "Nebeneffekt" zu qualifizieren ist, also erlauben es imperative Sprachen und deklarative nicht. Nach dem oben Gesagten und dem, was ich bekomme, beseitigt keine Sprache Nebenwirkungen. Entweder fehlt mir etwas über Nebenwirkungen, oder die Wikipedia-Definition ist falsch weit gefasst.

Codebot
quelle

Antworten:

26

Die funktionale Programmierung umfasst viele verschiedene Techniken. Einige Techniken sind gut mit Nebenwirkungen. Ein wichtiger Aspekt ist jedoch das Gleichungsdenken : Wenn ich eine Funktion mit demselben Wert aufrufe, erhalte ich immer das gleiche Ergebnis. So kann ich einen Funktionsaufruf durch den Rückgabewert ersetzen und ein gleichwertiges Verhalten erhalten. Dies erleichtert das Nachdenken über das Programm, insbesondere beim Debuggen.

Sollte die Funktion Nebenwirkungen haben, gilt dies nicht ganz. Der Rückgabewert entspricht nicht dem Funktionsaufruf, da der Rückgabewert keine Nebenwirkungen enthält.

Die Lösung wird unter Verwendung von stoppen Nebenwirkungen und Codieren dieser Effekte im Rückgabewert . Unterschiedliche Sprachen haben unterschiedliche Effektsysteme. Beispielsweise verwendet Haskell Monaden, um bestimmte Effekte wie E / A oder Zustandsmutation zu codieren. Die C / C ++ / Rust-Sprachen verfügen über ein Typsystem, das die Mutation einiger Werte nicht zulassen kann.

In einer imperativen Sprache print("foo")druckt eine Funktion etwas und gibt nichts zurück. In einer reinen Funktionssprache wie Haskell nimmt eine printFunktion auch ein Objekt, das den Zustand der Außenwelt darstellt, und gibt ein neues Objekt zurück, das den Zustand darstellt, nachdem diese Ausgabe ausgeführt wurde. Ähnliches wie newState = print "foo" oldState. Ich kann aus dem alten Zustand so viele neue Zustände erstellen, wie ich möchte. Es wird jedoch immer nur eine von der Hauptfunktion verwendet. Ich muss also die Zustände aus mehreren Aktionen sequenzieren, indem ich die Funktionen verkette. Zum Drucken foo barkönnte ich so etwas sagen print "bar" (print "foo" originalState).

Wenn kein Ausgabestatus verwendet wird, führt Haskell die Aktionen, die zu diesem Status führen, nicht aus, da es sich um eine faule Sprache handelt. Umgekehrt ist diese Faulheit nur möglich, weil alle Effekte explizit als Rückgabewerte codiert sind.

Beachten Sie, dass Haskell die einzige häufig verwendete Funktionssprache ist, die diese Route verwendet. Andere funktionale Sprachen inkl. Die Lisp-Familie, die ML-Familie und neuere funktionale Sprachen wie Scala entmutigen, lassen aber immer noch Nebenwirkungen zu - sie könnten als imperativ-funktionale Sprachen bezeichnet werden.

Die Verwendung von Nebenwirkungen für E / A ist wahrscheinlich in Ordnung. Oft werden E / A (außer Protokollierung) nur an der äußeren Grenze Ihres Systems ausgeführt. Innerhalb Ihrer Geschäftslogik findet keine externe Kommunikation statt. Es ist dann möglich, den Kern Ihrer Software in einem reinen Stil zu schreiben, während unreine E / A in einer äußeren Hülle ausgeführt werden. Dies bedeutet auch, dass der Kern zustandslos sein kann.

Staatenlosigkeit hat eine Reihe praktischer Vorteile, wie z. B. eine erhöhte Vernünftigkeit und Skalierbarkeit. Dies ist sehr beliebt für Webanwendungs-Backends. Jeder Status wird außerhalb in einer gemeinsam genutzten Datenbank gespeichert. Dies erleichtert den Lastausgleich: Ich muss keine Sitzungen an einen bestimmten Server binden. Was ist, wenn ich mehr Server benötige? Fügen Sie einfach eine weitere hinzu, da dieselbe Datenbank verwendet wird. Was ist, wenn ein Server abstürzt? Ich kann ausstehende Anforderungen auf einem anderen Server wiederholen. Natürlich gibt es noch Status - in der Datenbank. Aber ich habe es explizit gemacht und extrahiert und könnte intern einen rein funktionalen Ansatz verwenden, wenn ich möchte.

amon
quelle
Danke für die ausführliche Antwort. Was ich als Schlussfolgerung halte, ist, dass Nebenwirkungen den Funktionswert aufgrund von Gleichungsgründen nicht beeinflussen. Deshalb "erlauben / minimieren funktionale Sprachen keine Nebenwirkungen". In die Funktionswerte eingebettete Effekte wirken sich auf den Status aus und ändern ihn, der jemals gespeichert oder außerhalb des Programmkerns gespeichert wurde. E / A findet auch an der äußeren Grenze der Geschäftslogik statt.
Codebot
3
@codebot, meiner Meinung nach nicht ganz. Bei ordnungsgemäßer Implementierung sollten Nebenwirkungen in der Funktionsprogrammierung im Rückgabetyp der Funktion berücksichtigt werden. Wenn beispielsweise eine Funktion fehlschlägt (wenn eine bestimmte Datei nicht vorhanden ist oder keine Datenbankverbindung hergestellt werden kann), sollte der Rückgabetyp der Funktion den Fehler kapseln, anstatt dass die Funktion eine Ausnahme auslöst. Ein Beispiel finden Sie unter Eisenbahnorientierte Programmierung ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach
"... sie könnten als imperativ-funktionale Sprachen bezeichnet werden.": Simon Peyton Jones schrieb "... Haskell ist die beste imperative Programmiersprache der Welt."
Giorgio
5

Keine Programmiersprache beseitigt Nebenwirkungen. Ich denke, es ist besser zu sagen, dass deklarative Sprachen Nebenwirkungen enthalten, während imperative Sprachen dies nicht tun. Ich bin mir jedoch nicht so sicher, ob eines dieser Gespräche über Nebenwirkungen den grundlegenden Unterschied zwischen den beiden Arten von Sprachen zum Ausdruck bringt, und das scheint wirklich das zu sein, wonach Sie suchen.

Ich denke, es hilft, den Unterschied anhand eines Beispiels zu veranschaulichen.

a = b + c

Die obige Codezeile kann in praktisch jeder Sprache geschrieben werden. Wie können wir also feststellen, ob wir eine imperative oder deklarative Sprache verwenden? Wie unterscheiden sich die Eigenschaften dieser Codezeile in den beiden Sprachklassen?

In einer imperativen Sprache (C, Java, Javascript usw.) repräsentiert diese Codezeile lediglich einen Schritt in einem Prozess. Es sagt nichts über die fundamentale Natur eines der Werte aus. Es sagt uns, dass im Moment nach dieser Codezeile (aber vor der nächsten Zeile) agleich bplus ist, caber es sagt uns nichts aim weiteren Sinne.

In einer deklarativen Sprache (Haskell, Scheme, Excel usw.) sagt diese Codezeile viel mehr aus. Es stellt eine unveränderliche Beziehung zwischen aund den beiden anderen Objekten her, so dass immer der Fall agleich bplus ist c. Beachten Sie, dass ich Excel in die Liste der deklarativen Sprachen aufgenommen habe, da selbst wenn boder wenn csich der Wert ändert, die Tatsache bestehen bleibt, dass asie der Summe entspricht.

Meiner Meinung nach dieser , keine Nebenwirkungen oder Zustand, ist das, was die beiden Arten von Sprachen unterscheidet. In einer imperativen Sprache sagt eine bestimmte Codezeile nichts über die Gesamtbedeutung der betreffenden Variablen aus. Mit anderen Worten, a = b + cbedeutet nur, dass für einen sehr kurzen Moment adie Summe von bund gleich war c.

In deklarativen Sprachen legt jede Codezeile eine grundlegende Wahrheit fest, die während der gesamten Laufzeit des Programms existiert. a = b + cSagt Ihnen in diesen Sprachen, dass unabhängig davon, was in einer anderen Codezeile passiert, aimmer die Summe von bund gleich ist c.

Daniel T.
quelle