Ich glaube, dass Nebenwirkungen ein natürliches Phänomen sind. Aber in funktionalen Sprachen ist es so etwas wie ein Tabu. Was sind die Gründe?
Meine Frage ist spezifisch für den funktionalen Programmierstil. Nicht alle Programmiersprachen / Paradigmen.
functional-programming
side-effect
Gulshan
quelle
quelle
Antworten:
Das Schreiben Ihrer Funktionen / Methoden ohne Nebenwirkungen - es handelt sich also um reine Funktionen - erleichtert das Nachdenken über die Richtigkeit Ihres Programms.
Es macht es auch einfach, diese Funktionen zu komponieren, um ein neues Verhalten zu erzeugen.
Es ermöglicht auch bestimmte Optimierungen, bei denen der Compiler beispielsweise die Ergebnisse von Funktionen speichern oder die Eliminierung allgemeiner Unterausdrücke verwenden kann.
Bearbeiten: auf Benjols Wunsch: Da ein Großteil Ihres Status im Stapel gespeichert ist (Datenfluss, nicht Kontrollfluss, wie Jonas es hier genannt hat ), können Sie die Ausführung der Teile Ihrer Berechnung, die unabhängig von dieser sind, parallelisieren oder anderweitig neu anordnen gegenseitig. Sie können diese unabhängigen Teile leicht finden, da ein Teil dem anderen keine Eingaben bereitstellt.
In Umgebungen mit Debuggern, mit denen Sie den Stack zurücksetzen und die Datenverarbeitung wieder aufnehmen können (wie z. B. Smalltalk), können Sie mithilfe reiner Funktionen sehr leicht feststellen, wie sich ein Wert ändert, da die vorherigen Status zur Überprüfung zur Verfügung stehen. Wenn Sie in einer mutationsintensiven Berechnung Ihrer Struktur oder Ihrem Algorithmus nicht explizit Aktionen zum Ausführen / Rückgängigmachen hinzufügen, wird der Verlauf der Berechnung nicht angezeigt. (Dies knüpft an den ersten Absatz an: Das Schreiben reiner Funktionen erleichtert die Überprüfung der Richtigkeit Ihres Programms.)
quelle
Aus einem Artikel über funktionale Programmierung :
quelle
Sie haben es falsch verstanden, funktionale Programmierung fördert die Begrenzung von Nebenwirkungen, damit Programme einfach zu verstehen und zu optimieren sind. Sogar mit Haskell können Sie in Dateien schreiben.
Im Wesentlichen sage ich, dass funktionierende Programmierer Nebenwirkungen nicht für böse halten, sondern lediglich die Einschränkung der Verwendung von Nebenwirkungen für gut halten. Ich weiß, es mag so einfach erscheinen, aber es macht den Unterschied.
quelle
readFile
tun, ist eine Abfolge von Aktionen zu definieren. Diese Sequenz ist funktional rein und ähnelt einem abstrakten Baum, der beschreibt, was zu tun ist. Die eigentlichen Dirty Side Effects werden dann von der Runtime ausgeführt.Ein paar Anmerkungen:
Funktionen ohne Nebenwirkungen können trivialerweise parallel ausgeführt werden, während Funktionen mit Nebenwirkungen normalerweise eine Art Synchronisation erfordern.
Funktionen ohne Nebenwirkungen ermöglichen eine aggressivere Optimierung (z. B. durch transparentes Verwenden eines Ergebnis-Caches), da es egal ist, ob die Funktion tatsächlich ausgeführt wurde oder nicht, solange wir das richtige Ergebnis erhalten
quelle
deterministic
Klausel für Funktionen ohne Nebenwirkungen, so dass sie nicht öfter als nötig ausgeführt werden.deterministic
Klausel ist nur ein Schlüsselwort, das dem Compiler mitteilt, dass dies eine deterministische Funktion ist, vergleichbar mit demfinal
Schlüsselwort in Java, das dem Compiler mitteilt, dass die Variable nicht geändert werden kann.Ich arbeite jetzt hauptsächlich mit funktionalem Code, und aus dieser Perspektive scheint es verblüffend offensichtlich. Nebenwirkungen verursachen eine große mentale Belastung für Programmierer, die versuchen, Code zu lesen und zu verstehen. Sie bemerken diese Belastung erst, wenn Sie eine Weile davon frei sind, und müssen dann plötzlich wieder Code mit Nebenwirkungen lesen.
Betrachten Sie dieses einfache Beispiel:
Ich weiß, dass
foo
es in einer funktionalen Sprache immer noch 42 sind. Ich muss mir nicht einmal den Code dazwischen ansehen, geschweige denn ihn verstehen oder mir die Implementierungen der Funktionen ansehen, die er aufruft.Alles, was mit Parallelität, Parallelisierung und Optimierung zu tun hat, ist nett, aber genau das haben Informatiker in die Broschüre aufgenommen. Ich muss mich nicht wundern, wer Ihre Variable mutiert und wann das ist, was mir beim täglichen Üben wirklich Spaß macht.
quelle
Nur wenige Sprachen machen es unmöglich, Nebenwirkungen zu verursachen. Völlig nebenwirkungsfreie Sprachen wären mit Ausnahme einer sehr begrenzten Kapazität nur schwer (fast unmöglich) zu gebrauchen.
Weil sie es viel schwieriger machen, genau zu überlegen, was ein Programm tut, und zu beweisen, dass es das tut, was Sie von ihm erwarten.
Stellen Sie sich vor, Sie testen eine gesamte dreistufige Website mit nur Black-Box-Tests. Klar, es ist machbar, je nach Maßstab. Aber es wird sicherlich viel dupliziert. Und wenn es ist ein Bug (das auf einen Nebeneffekt verbunden ist), dann könnte man das ganze System brechen für weitere Tests, bis der Fehler diagnostiziert und fixiert ist , und das Update auf die Testumgebung bereitgestellt.
Leistungen
Nun verkleinere das. Wenn Sie ziemlich gut im Schreiben von nebenwirkungsfreiem Code wären, wie viel schneller könnten Sie über das nachdenken, was ein vorhandener Code getan hat? Wie viel schneller könnten Sie Unit-Tests schreiben? Wie zuversichtlich würden Sie sich fühlen , dass der Code ohne Nebenwirkungen wurde fehlerfrei gewährleistet ist , und dass die Nutzer ihre Exposition gegenüber irgendwelchen Fehler begrenzen könnte es haben ? Als
Wenn der Code keine Nebenwirkungen hat, verfügt der Compiler möglicherweise auch über zusätzliche Optimierungen, die er durchführen könnte. Möglicherweise ist es viel einfacher, diese Optimierungen zu implementieren. Es ist möglicherweise viel einfacher, eine Optimierung für nebenwirkungsfreien Code zu konzipieren. Dies bedeutet, dass Ihr Compiler-Anbieter möglicherweise Optimierungen implementiert, die in Code mit Nebenwirkungen nur schwer oder gar nicht möglich sind.
Parallelität ist auch drastisch einfacher zu implementieren, automatisch zu generieren und zu optimieren, wenn Code keine Nebenwirkungen hat. Dies liegt daran, dass alle Teile in beliebiger Reihenfolge sicher bewertet werden können. Es wird allgemein als die nächste große Herausforderung der Informatik angesehen, Programmierern das Schreiben von hochkonkurrierendem Code zu ermöglichen, und dies ist eine der wenigen verbleibenden Absicherungen gegen Moores Gesetz .
quelle
Nebenwirkungen sind wie "Lecks" in Ihrem Code, die später entweder von Ihnen oder einem ahnungslosen Mitarbeiter behoben werden müssen.
Funktionale Sprachen vermeiden Zustandsvariablen und veränderbare Daten, um den Code weniger kontextabhängig und modularer zu machen. Modularität stellt sicher, dass die Arbeit eines Entwicklers die Arbeit eines anderen Entwicklers nicht beeinträchtigt oder untergräbt.
Das Skalieren der Entwicklungsrate mit der Teamgröße ist heutzutage ein "heiliger Gral" der Softwareentwicklung. Bei der Arbeit mit anderen Programmierern sind nur wenige Dinge so wichtig wie die Modularität. Selbst die einfachsten logischen Nebenwirkungen erschweren die Zusammenarbeit erheblich.
quelle
Nun, meiner Meinung nach ist das ziemlich heuchlerisch. Niemand mag Nebenwirkungen, aber jeder braucht sie.
Das Gefährliche an Nebenwirkungen ist, dass ein Funktionsaufruf möglicherweise nicht nur Auswirkungen auf das Verhalten der Funktion beim nächsten Aufruf hat, sondern möglicherweise auch auf andere Funktionen. Somit führen Nebenwirkungen zu unvorhersehbarem Verhalten und nichttrivialen Abhängigkeiten.
Programmierparadigmen wie OO und Functional lösen dieses Problem. OO reduziert das Problem durch Auferlegung einer Trennung von Bedenken. Dies bedeutet, dass der Anwendungszustand, der aus vielen veränderlichen Daten besteht, in Objekte eingekapselt wird, von denen jedes nur für die Aufrechterhaltung seines eigenen Zustands verantwortlich ist. Auf diese Weise wird das Risiko von Abhängigkeiten verringert und Probleme werden viel isolierter und leichter zu verfolgen.
Die funktionale Programmierung ist weitaus radikaler, da der Anwendungszustand aus Sicht des Programmierers einfach unveränderlich ist. Das ist eine nette Idee, macht aber die Sprache für sich unbrauchbar. Warum? Weil JEDE I / O-Operation Nebenwirkungen hat. Sobald Sie aus einem Eingabestream lesen, wird sich der Anwendungsstatus wahrscheinlich ändern, da das Ergebnis beim nächsten Aufrufen derselben Funktion wahrscheinlich anders ist. Möglicherweise lesen Sie andere Daten, oder - möglicherweise - schlägt der Vorgang fehl. Gleiches gilt für die Ausgabe. Sogar die Ausgabe ist eine Operation mit Nebenwirkungen. Das ist nichts, was Sie heutzutage oft bemerken, aber stellen Sie sich vor, Sie haben nur 20 KB für Ihre Ausgabe, und wenn Sie mehr ausgeben, stürzt Ihre App ab, weil Ihnen der Speicherplatz fehlt oder was auch immer.
Also ja, aus der Sicht eines Programmierers sind Nebenwirkungen böse und gefährlich. Die meisten Fehler rühren von der Art und Weise her, in der bestimmte Teile des Anwendungszustands durch unüberlegte und oftmals unnötige Nebenwirkungen auf nahezu undurchsichtige Weise miteinander verzahnt sind. Aus der Sicht eines Benutzers sind Nebenwirkungen der Punkt, an dem ein Computer verwendet wird. Sie kümmern sich nicht darum, was im Inneren passiert oder wie es organisiert ist. Sie tun etwas und erwarten, dass sich der Computer entsprechend ändert.
quelle
Jeder Nebeneffekt führt zusätzliche Eingabe- / Ausgabeparameter ein, die beim Testen berücksichtigt werden müssen.
Dies macht die Codevalidierung viel komplexer, da die Umgebung nicht nur auf den zu validierenden Code beschränkt sein kann, sondern auch einige oder alle umgebenden Umgebungen einbeziehen muss (der aktualisierte globale Code befindet sich in diesem Code, was wiederum davon abhängt Code, der wiederum davon abhängt, in einem vollständigen Java EE-Server zu leben ....)
Indem Sie versuchen, Nebenwirkungen zu vermeiden, begrenzen Sie den Umfang des Externalismus, der zum Ausführen des Codes erforderlich ist.
quelle
Nach meiner Erfahrung erfordert gutes Design in der objektorientierten Programmierung die Verwendung von Funktionen, die Nebenwirkungen haben.
Nehmen Sie zum Beispiel eine grundlegende UI-Desktopanwendung. Ich habe möglicherweise ein laufendes Programm, auf dessen Heap sich ein Objektdiagramm befindet, das den aktuellen Status des Domänenmodells meines Programms darstellt. Nachrichten gelangen zu den Objekten in diesem Diagramm (z. B. über Methodenaufrufe, die vom UI-Layer-Controller aufgerufen werden). Das Objektdiagramm (Domänenmodell) auf dem Heap wird als Antwort auf die Nachrichten geändert. Beobachter des Modells werden über Änderungen informiert, die Benutzeroberfläche und möglicherweise andere Ressourcen werden geändert.
Weit davon entfernt, böse zu sein, bildet die richtige Anordnung dieser haufen- und bildschirmmodifizierenden Nebenwirkungen den Kern des OO-Designs (in diesem Fall das MVC-Muster).
Das bedeutet natürlich nicht, dass Ihre Methoden unerwünschte Nebenwirkungen haben sollten. Nebenwirkungsfreie Funktionen können die Lesbarkeit und manchmal auch die Leistung Ihres Codes verbessern.
quelle
Das Böse ist etwas übertrieben. Es hängt alles vom Kontext des Sprachgebrauchs ab.
Eine weitere Überlegung zu den bereits erwähnten ist, dass es Beweise für die Richtigkeit eines Programms viel einfacher macht, wenn es keine funktionellen Nebenwirkungen gibt.
quelle
Wie die obigen Fragen gezeigt haben, verhindern funktionale Sprachen nicht so sehr, dass Code Nebenwirkungen hat, sondern bieten uns Tools, mit denen wir steuern können, welche Nebenwirkungen in einem bestimmten Codeteil auftreten können und wann.
Dies hat sehr interessante Konsequenzen. Erstens und am offensichtlichsten gibt es zahlreiche Dinge, die Sie mit nebenwirkungsfreiem Code tun können, die bereits beschrieben wurden. Aber wir können auch andere Dinge tun, selbst wenn wir mit Code arbeiten, der Nebenwirkungen hat:
quelle
In komplexen Codebasen sind komplexe Wechselwirkungen von Nebenwirkungen das Schwierigste, über das ich nachdenken kann. Ich kann nur persönlich sprechen, wenn mein Gehirn funktioniert. Nebenwirkungen und anhaltende Zustände und mutierende Eingaben usw. veranlassen mich, über das "Wann" und "Wo" nachzudenken, und nicht nur über das "Was", das in jeder einzelnen Funktion geschieht.
Ich kann mich nicht nur auf "was" konzentrieren. Ich kann nach gründlichem Testen einer Funktion, die Nebenwirkungen verursacht, nicht den Schluss ziehen, dass sie mit ihrem Code einen Hauch von Zuverlässigkeit verbreitet, da Aufrufer sie möglicherweise immer noch missbrauchen, indem sie sie zur falschen Zeit, aus dem falschen Thread, im falschen aufrufen Bestellung. In der Zwischenzeit ist es so gut wie unmöglich, eine Funktion, die keine Nebenwirkungen hervorruft und bei einer Eingabe nur eine neue Ausgabe zurückgibt (ohne die Eingabe zu berühren), auf diese Weise zu missbrauchen.
Aber ich bin ein pragmatischer Typ, denke ich, oder versuche es zumindest, und ich denke nicht, dass wir alle Nebenwirkungen auf ein Minimum reduzieren müssen, um über die Richtigkeit unseres Codes nachzudenken (zumindest) Ich würde es sehr schwierig finden, dies in Sprachen wie C) zu tun. Ich finde es sehr schwierig, über die Korrektheit zu urteilen, wenn wir eine Kombination aus komplexen Kontrollabläufen und Nebenwirkungen haben.
Komplexe Steuerungsabläufe sind für mich diejenigen, die grafischer Natur sind, oft rekursiv oder rekursiv (Ereigniswarteschlangen, die z. B. Ereignisse nicht direkt rekursiv aufrufen, sondern "rekursiv" sind) und möglicherweise Dinge tun Beim Durchlaufen einer tatsächlich verknüpften Diagrammstruktur oder beim Verarbeiten einer inhomogenen Ereigniswarteschlange, die eine eklektische Mischung von zu verarbeitenden Ereignissen enthält, die uns zu den unterschiedlichsten Teilen der Codebasis führen und unterschiedliche Nebenwirkungen auslösen. Wenn Sie versuchen würden, alle Stellen herauszuarbeiten, an denen Sie letztendlich im Code landen, würde dies einem komplexen Diagramm ähneln und möglicherweise Knoten in dem Diagramm enthalten, von denen Sie nie erwartet hätten, dass sie in diesem bestimmten Moment vorhanden waren und dass sie alle vorhanden sind Nebenwirkungen verursachen,
Funktionale Sprachen können äußerst komplexe und rekursive Kontrollabläufe haben, aber das Ergebnis ist in Bezug auf die Korrektheit so einfach zu verstehen, da dabei nicht alle möglichen eklektischen Nebenwirkungen auftreten. Nur wenn komplexe Kontrollabläufe auf eklektische Nebenwirkungen stoßen, empfinde ich es als kopfschmerzanregend, zu versuchen, die Gesamtheit der Vorgänge zu erfassen und festzustellen, ob sie immer das Richtige bewirken.
Wenn ich solche Fälle habe, finde ich es oft sehr schwierig, wenn nicht unmöglich, sehr sicher zu sein, dass ein solcher Code korrekt ist, geschweige denn, dass ich Änderungen an diesem Code vornehmen kann, ohne auf etwas Unerwartetes zu stoßen. Die Lösung für mich ist entweder, den Kontrollfluss zu vereinfachen oder die Nebenwirkungen zu minimieren / zu vereinheitlichen (unter Vereinheitlichung verstehe ich, dass in einer bestimmten Phase des Systems nur eine Art von Nebenwirkung auf viele Dinge einwirkt, nicht zwei oder drei oder a Dutzend). Ich brauche eines dieser beiden Dinge, damit mein simples Gehirn sich sicher fühlen kann, dass der vorhandene Code korrekt ist und die von mir eingeführten Änderungen korrekt sind. Es ist ziemlich einfach, sich auf die Richtigkeit des Codes zu verlassen, der Nebenwirkungen hervorruft, wenn die Nebenwirkungen zusammen mit dem Kontrollfluss einheitlich und einfach sind.
Es ist ziemlich einfach, über die Korrektheit eines solchen Codes nachzudenken, aber hauptsächlich, weil die Nebenwirkungen so gleichmäßig sind und der Kontrollfluss so einfach ist. Angenommen, wir hatten Code wie folgt:
Dann ist dies ein lächerlich stark vereinfachter Pseudocode, der normalerweise viel mehr Funktionen und verschachtelte Schleifen und viel mehr Dinge beinhalten würde (Aktualisierung mehrerer Texturabbildungen, Knochengewichte, Auswahlzustände usw.), aber selbst der Pseudocode macht dies so schwierig Grund für die Richtigkeit wegen des Zusammenspiels des komplexen grafischen Kontrollflusses und der auftretenden Nebenwirkungen. Eine Strategie zur Vereinfachung besteht darin, die Verarbeitung aufzuschieben und sich jeweils nur auf eine Art von Nebeneffekten zu konzentrieren:
... etwas in diesem Sinne als eine Iteration der Vereinfachung. Das bedeutet, dass wir die Daten mehrmals durchlaufen, was definitiv einen Rechenaufwand bedeutet, aber wir stellen häufig fest, dass wir den resultierenden Code leichter multithreaden können, da die Nebeneffekte und Kontrollabläufe diese einheitliche und einfachere Natur angenommen haben. Darüber hinaus kann jede Schleife cachefreundlicher gestaltet werden, als den verbundenen Graphen zu durchlaufen und dabei Nebenwirkungen hervorzurufen (Beispiel: Verwenden Sie ein paralleles Bit-Set, um zu markieren, was durchlaufen werden muss, damit wir die verzögerten Durchläufe in sortierter Reihenfolge ausführen können mit Bitmasken und FFS). Aber am wichtigsten ist, dass ich finde, dass die zweite Version in Bezug auf Korrektheit und Änderung viel einfacher zu überlegen ist, ohne Fehler zu verursachen. Damit'
Und schließlich müssen irgendwann Nebenwirkungen auftreten, sonst hätten wir nur Funktionen, die Daten ausgeben, die nirgendwo hingehen können. Oft müssen wir etwas in eine Datei aufnehmen, etwas auf einem Bildschirm anzeigen, die Daten über einen Socket übertragen, etwas in dieser Art, und all diese Dinge sind Nebenwirkungen. Aber wir können definitiv die Anzahl der überflüssigen Nebenwirkungen reduzieren, die auftreten, und auch die Anzahl der Nebenwirkungen, die auftreten, wenn die Kontrollabläufe sehr kompliziert sind, und ich denke, es wäre viel einfacher, Fehler zu vermeiden, wenn wir dies tun würden.
quelle
Es ist nicht böse. Meiner Meinung nach ist es notwendig, die beiden Funktionstypen zu unterscheiden - mit und ohne Nebenwirkungen. Die Funktion ohne Nebeneffekte: - liefert immer das Gleiche mit den gleichen Argumenten, so dass zum Beispiel eine solche Funktion ohne Argumente keinen Sinn ergibt. - Das heißt auch, dass die Reihenfolge, in der solche Funktionen aufgerufen werden, keine Rolle spielt - muss lauffähig sein und darf allein (!) Ohne weiteren Code debuggt werden. Und nun, lol, schau, was JUnit macht. Eine Funktion mit Nebenwirkungen: - Hat Art von "Lecks", was automatisch hervorgehoben werden kann. - Es ist sehr wichtig, Fehler zu beheben und zu suchen, die im Allgemeinen durch Nebenwirkungen verursacht werden. - Jede Funktion mit Nebenwirkungen hat auch einen "Teil" von sich ohne Nebenwirkungen, was auch automatisch getrennt werden kann. So böse sind diese Nebenwirkungen,
quelle