Frage

Ich bin der Meinung, dass Nebenwirkungen ein natürliches Phänomen sind. Aber es ist so etwas wie Tabu in funktionalen Sprachen. Was sind die Gründe?

Meine Frage ist spezifisch für den funktionalen Programmierstil. Nicht alle Programmiersprachen/Paradigmen.

War es hilfreich?

Lösung

Schreiben Sie Ihre Funktionen/Methoden ohne Nebenwirkungen - also sind sie sie reine Funktionen - Erleichtert es einfacher, die Richtigkeit Ihres Programms zu begründen.

Es macht es auch einfach, diese Funktionen zu komponieren, um neues Verhalten zu erstellen.

Es ermöglicht auch bestimmte Optimierungen, bei denen der Compiler beispielsweise die Ergebnisse von Funktionen einhalten oder eine gemeinsame Eliminierung der Unterexpression verwenden kann.

Bearbeiten: Auf Anfrage von Benjol: Weil ein Großteil Ihres Staates im Stapel gespeichert ist (Datenfluss, nicht Steuerfluss, wie Jonas es genannt hat hier), Sie können die Ausführung dieser Teile Ihrer Berechnung parallelisieren oder anderweitig neu ordnen, die voneinander unabhängig sind. Sie können diese unabhängigen Teile leicht finden, da ein Teil keine Eingaben für den anderen bereitstellt.

In Umgebungen mit Debuggern, in denen Sie den Stapel und das Wiederaufleben von Computing (wie SmallTalk) zurückrollen können, können Sie reine Funktionen haben, dass Sie sehr leicht erkennen können, wie sich ein Wert ändert, da die vorherigen Zustände zur Inspektion verfügbar sind. In einer mutationsorientierten Berechnung können Sie die Geschichte der Berechnung nicht sehen, wenn Sie Ihrer Struktur oder Ihrem Algorithmus nicht ausdrücklich Do/Ungut-Aktionen hinzufügen. (Dies verbindet den ersten Absatz: Das Schreiben reine Funktionen erleichtert es einfacher zu prüfen die Richtigkeit Ihres Programms.)

Andere Tipps

Von einem Artikel über Funktionelle Programmierung:

In der Praxis müssen Anwendungen einige Nebenwirkungen haben. Simon Peyton-Jones, ein wichtiger Beitrag zur funktionalen Programmiersprache Haskell, sagte Folgendes: "Am Ende muss jedes Programm den Zustand manipulieren. Ein Programm, das überhaupt keine Nebenwirkungen hat, ist eine Art Black-Box. Alles, was Sie sagen können, ist alles Dass die Box heißer wird. " (http://oscon.blip.tv/file/324976) Der Schlüssel besteht darin, Nebenwirkungen zu begrenzen, sie klar zu identifizieren und sie im gesamten Code zu verstreuen.

Sie haben es falsch gemacht, funktionale Programmierung fördert die begrenzten Nebenwirkungen, um Programme leicht zu verstehen und zu optimieren. Sogar Haskell können Sie in Dateien schreiben.

Ich sage im Wesentlichen, dass funktionelle Programmierer nicht der Meinung sind, dass Nebenwirkungen böse sind, sie denken einfach, dass die Verwendung von Nebenwirkungen einschränken ist. Ich weiß, es mag eine so einfache Unterscheidung erscheinen, aber es macht den Unterschied.

Ein paar Hinweise:

  • Funktionen ohne Nebenwirkungen können parallel ausgestellt werden, während Funktionen mit Nebenwirkungen in der Regel eine Art Synchronisation erfordern.

  • Funktionen ohne Nebenwirkungen ermöglichen eine aggressivere Optimierung (z. B. durch transparente Verwendung eines Ergebniscache), denn solange wir das richtige Ergebnis erzielen, spielt es keine Rolle, ob die Funktion war oder nicht Ja wirklich hingerichtet

Ich arbeite jetzt hauptsächlich im Funktionscode, und aus dieser Perspektive scheint es blendend offensichtlich. Nebenwirkungen erzeugen a riesig Mentale Belastung für Programmierer, die versuchen, Code zu lesen und zu verstehen. Sie bemerken diese Belastung nicht, bis Sie eine Weile frei davon sind, und müssen dann plötzlich den Code erneut mit Nebenwirkungen lesen.

Betrachten Sie dieses einfache Beispiel:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

In einer funktionalen Sprache, ich kennt das foo ist immer noch 42. Ich muss nicht einmal sehen Im Code dazwischen, viel weniger verstehen oder die Implementierungen der von ihm aufgerufenen Funktionen betrachten.

All das Zeug über Parallelität und Parallelisierung und Optimierung ist schön, aber das ist es, was Informatiker auf die Broschüre setzen. Ich muss mich nicht fragen, wer deine Variable mutiert und wann ist das, was ich in der täglichen Praxis wirklich genieße.

Nur wenige bis keine Sprachen machen es unmöglich, Nebenwirkungen zu verursachen. Sprachen, die völlig seitlich frei sind, wären unerschwinglich schwierig (nahezu unmöglich) zu verwenden, außer in sehr begrenzter Kapazität.

Warum werden Nebenwirkungen als böse angesehen?

Weil sie es viel schwieriger machen, genau zu argumentieren, was ein Programm tut, und zu beweisen, dass es das tut, was Sie erwarten.

Stellen Sie sich vor, Sie testen auf einer sehr hohen Ebene eine gesamte 3-stufige Website mit nur Black-Box-Tests. Sicher, es ist abhängig von der Skala machbar. Aber es gibt sicherlich viel Duplizierung. Und wenn da ist Ein Fehler (der sich mit einem Nebeneffekt zusammenhängt), können Sie möglicherweise das gesamte System für weitere Tests brechen, bis der Fehler diagnostiziert und behoben wird und der Fix in der Testumgebung bereitgestellt wird.

Vorteile

Jetzt skalieren Sie das. Wenn Sie ziemlich gut darin sind, einen kostenlosen Code von Nebeneffekten zu schreiben, wie viel schneller würden Sie zu dem argumentieren, was ein vorhandener Code getan hat? Wie viel schneller könnten Sie Unit -Tests schreiben? Wie sicher würden Sie das Gefühl haben, dass der Code ohne Nebenwirkungen fehlerfrei garantiert war und dass Benutzer ihre Exposition gegenüber allen Fehler einschränken könnten tat haben?

Wenn Code keine Nebenwirkungen hat, kann der Compiler auch zusätzliche Optimierungen haben, die er durchführen könnte. Es kann viel einfacher sein, diese Optimierungen zu implementieren. Es kann viel einfacher sein, eine Optimierung für den freien Code von Nebeneffekten zu konzipieren, was bedeutet, dass Ihr Compiler-Anbieter Optimierungen implementieren kann, die in Code mit Nebenwirkungen schwer zu unheilbar sind.

Die 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. Die Ermöglichung von Programmierern, einen hochkomplizierten Code zu schreiben, wird allgemein als die nächste große Herausforderung angesehen, die Informatik angehen muss Moores Gesetz.

Nebenwirkungen sind wie "Lecks" in Ihrem Code, die später von Ihnen oder einem ahnungslosen Kollegen behandelt werden müssen.

Funktionssprachen vermeiden Zustandsvariablen und veränderliche Daten, um Code weniger kontextabhängiger und modularer zu gestalten. Die Modularität versichert, dass die Arbeit eines Entwicklers die Arbeit eines anderen nicht beeinflusst/untergräbt.

Die Skalierungsrate der Entwicklung mit Teamgröße ist heute ein "heiliger Gral" der Softwareentwicklung. Bei der Arbeit mit anderen Programmierern sind nur wenige Dinge genauso wichtig wie Modularität. Selbst die einfachsten logischen Nebenwirkungen machen die Zusammenarbeit äußerst schwierig.

Nun, IMHO, das ist ziemlich scheinheilig. Niemand mag Nebenwirkungen, aber jeder braucht sie.

Was an Nebenwirkungen so gefährlich ist, ist, dass dies, wenn Sie eine Funktion aufrufen, dies möglicherweise nicht nur auf die Art und Weise wirkt, wie sich die Funktion verhält, wenn sie beim nächsten Mal aufgerufen wird, sondern möglicherweise diesen Effekt auf andere Funktionen hat. Somit führen Nebenwirkungen unvorhersehbares Verhalten und nichttriviale Abhängigkeiten ein.

Programmierparadigmen wie OO und Funktionalbeschwerden befassen sich beide mit diesem Problem. OO reduziert das Problem, indem sie eine Trennung von Bedenken auferlegt. Dies bedeutet, dass der Anwendungszustand, der aus vielen veränderlichen Daten besteht, in Objekte eingekapselt ist, von denen jede nur für die Aufrechterhaltung eines eigenen Zustands verantwortlich ist. Auf diese Weise wird das Risiko von Abhängigkeiten verringert und die Probleme sind weitaus isolierter und leichter zu verfolgen.

Die funktionelle Programmierung verfolgt einen weitaus radikaleren Ansatz, bei dem der Anwendungszustand aus der Perspektive des Programmierers einfach unveränderlich ist. Dies ist eine nette Idee, aber die Sprache für sich selbst nutzlos macht. Wieso den? Weil jede I/O-Operation Nebenwirkungen hat. Sobald Sie aus einem Eingabestream gelesen werden, wird sich Ihr Anwendungsstatus wahrscheinlich ändern, da das Ergebnis beim nächsten Mal wahrscheinlich unterschiedlich ist. Möglicherweise lesen Sie verschiedene Daten oder - auch eine Möglichkeit -, dass die Operation möglicherweise fehlschlägt. Gleiches gilt für die Ausgabe. Sogar die Ausgabe ist ein Betrieb mit Nebenwirkungen. Dies ist nichts, was Sie heutzutage oft erkennen, aber stellen Sie sich vor, Sie haben nur 20.000 für Ihre Ausgabe, und wenn Sie mehr ausgeben, stürzt Ihre App ab, weil Sie keinen Speicherplatz mehr haben oder was auch immer.

Ja, Nebenwirkungen sind aus der Perspektive eines Programmierers böse und gefährlich. Die meisten Fehler stammen aus der Art und Weise, wie bestimmte Teile des Anwendungszustands auf nahezu unklar sind, durch unbestreitete und oft unnötige Nebenwirkungen. Aus der Perspektive eines Benutzers sind Nebenwirkungen die Verwendung eines Computers. Es ist ihnen egal, was im Inneren passiert oder wie es organisiert ist. Sie tun etwas und erwarten, dass sich der Computer entsprechend ändert.

Jeder Nebeneffekt führt zusätzliche Eingangs-/Ausgangsparameter ein, die beim Testen in Rechnung gestellt werden müssen.

Dies macht die Code -Validierung viel komplexer, da die Umgebung nicht nur auf den validierten Code beschränkt sein kann, sondern einige oder die gesamte Umgebung einbringen muss (das globale, das in diesem Code aktualisiert wird, was wiederum davon abhängt, davon abhängt Code, der wiederum davon abhängt, in einem vollen Java EE -Server zu leben ....)

Wenn Sie versuchen, Nebenwirkungen zu vermeiden, begrenzen Sie die Menge an Externalismus, die zum Ausführen des Code erforderlich sind.

Meiner Erfahrung nach Mandates Gutes Design in objektorientierten Programmiermandates die Verwendung von Funktionen mit Nebenwirkungen.

Nehmen Sie beispielsweise eine grundlegende UI -Desktop -Anwendung an. Ich habe möglicherweise ein laufendes Programm, das auf seinem Heap ein Objektdiagramm enthält, das den aktuellen Stand des Domänenmodells meines Programms darstellt. Nachrichten kommen zu den Objekten in diesem Diagramm an (zum Beispiel über Methodenaufrufe, die vom UI -Schicht -Controller aufgerufen wurden). Das Objektdiagramm (Domänenmodell) auf dem Heap wird als Antwort auf die Nachrichten geändert. Beobachter des Modells werden über Änderungen, die Benutzeroberfläche und möglicherweise andere Ressourcen geändert.

Weit davon entfernt, böse zu sein, sind die korrekte Anordnung dieser haufen-modifizierenden und screenmodifizierenden Nebenwirkungen im Kern des OO-Designs (in diesem Fall das MVC-Muster).

Das bedeutet natürlich nicht, dass Ihre Methoden eine willkürlichen Nebenwirkungen haben sollten. Die freien Funktionen für Nebeneffekte haben einen Platz bei der Verbesserung der Lesbarkeit und manchmal auch der Leistung Ihres Codes.

Das Böse ist ein bisschen übertrieben. Es hängt alles vom Kontext der Verwendung der Sprache ab.

Eine weitere Überlegung für die bereits erwähnten Personen ist, dass es viel einfacher ist, die Korrektheit eines Programms zu korrigieren, wenn es keine funktionalen Nebenwirkungen gibt.

Wie oben erwähnt, haben funktionale Sprachen nicht so viel verhindern Code von Nebenwirkungen als uns liefern wir Tools zum Verwalten, welche Nebenwirkungen in einem bestimmten Code und wann passieren können.

Dies hat sehr interessante Konsequenzen. Zunächst und am offensichtlichsten gibt es zahlreiche Dinge, die Sie mit Nebeneffekt-freien Code machen können, die bereits beschrieben wurden. Aber es gibt auch andere Dinge, die wir tun können, auch wenn wir mit Code arbeiten, die Nebenwirkungen haben:

  • In Code mit mutablen Zustand können wir den Umfang des Staates so verwalten, dass er statisch sicherstellt Seien Sie dennoch sicher, dass keine Referenzen überleben. Die gleichen Garantien sind auch nützlich für die Aufrechterhaltung von Datenschutzinformationen usw. (dies kann mit dem ST Monad in Haskell erreicht werden)
  • Wenn wir den gemeinsamen Status in mehreren Threads ändern, können wir die Notwendigkeit von Sperren vermeiden, indem wir Änderungen verfolgen und ein Atomaktualisierung am Ende einer Transaktion durchführen oder die Transaktion zurückrollen und wiederholen, wenn ein anderer Thread eine widersprüchliche Änderung vorgenommen hat. Dies ist nur erreichbar, da wir sicherstellen können, dass der Code keine anderen Auswirkungen hat als die staatlichen Änderungen (die wir gerne aufgeben können). Dies wird von der STM (Software Transactional Memory) Monad in Haskell durchgeführt.
  • Wir können die Auswirkungen von Code und trivial Sandkasten verfolgen, um alle Auswirkungen zu filtern, um sicherzustellen, dass es sicher ist, und dies zulässt (zum Beispiel). Benutzereingabecode, der auf einer Website sicher ausgeführt werden soll

In komplexen Code -Basen sind komplexe Wechselwirkungen von Nebenwirkungen die schwierigste, die ich finde. Ich kann nur persönlich angegeben, wie mein Gehirn funktioniert. Nebenwirkungen und anhaltende Zustände und mutierende Inputs usw. machen mich über "wann" und "Wo" Dinge über die Korrektheit der Grundlegender, nicht nur "was" in jeder einzelnen Funktion geschieht.

Ich kann mich nicht einfach auf "Was" konzentrieren. Ich kann nach dem gründlichen Testen einer Funktion nicht zu dem Schluss kommen bestellen. In der Zwischenzeit ist eine Funktion, die keine Nebenwirkungen verursacht und nur eine neue Ausgabe zurückgibt, wenn eine Eingabe (ohne die Eingabe zu berühren), auf diese Weise ziemlich unmöglich zu missbrauchen.

Aber ich bin ein pragmatischer Typ, denke ich oder versuche es zumindest zu sein, und ich glaube nicht Ich würde das in Sprachen wie C sehr schwierig finden. Wo ich es sehr schwierig finde, über Korrektheit zu argumentieren, ist, wenn wir die Kombination komplexer Kontrollströme und Nebenwirkungen haben.

Komplexe Kontrollströme für mich sind diejenigen, die in der Natur grafisch sind, oft rekursiv oder rekursiv wie Warteschlangen (Ereigniswarteschlangen, z. Um eine tatsächliche verknüpfte Diagrammstruktur zu durchqueren oder eine nicht-homogene Ereigniswarteschlange zu verarbeiten, die eine vielseitige Mischung von Ereignissen enthält, um uns zu allen Arten von Teilen der Codebasis und allen Auslösen verschiedener Nebenwirkungen zu verarbeiten. Wenn Sie versuchen würden, alle Orte auszuziehen, werden Sie letztendlich in den Code landen, er wäre einem komplexen Diagramm und möglicherweise mit Knoten in dem Diagramm, das Sie nie erwartet hätten Wenn Sie Nebenwirkungen verursachen, sind Sie möglicherweise nicht nur überrascht, welche Funktionen aufgerufen werden, sondern auch welche Nebenwirkungen in dieser Zeit und in der Reihenfolge, in der sie auftreten, auftreten.

Funktionelle Sprachen können äußerst komplexe und rekursive Kontrollflüsse haben, aber das Ergebnis ist in Bezug auf die Richtigkeit so einfach zu verstehen, da es nicht alle möglichen eklektischen Nebenwirkungen gibt. Nur wenn komplexe Kontrollströme vielseitige Nebenwirkungen erfüllen, finde ich es Kopfschmerzen, um zu versuchen, die Gesamtheit zu verstehen, was los ist und ob es immer das Richtige tun wird.

Wenn ich diese Fälle habe, fällt es mir oft sehr schwierig, wenn nicht unmöglich, sehr zuversichtlich über die Richtigkeit eines solchen Codes zu sein, geschweige denn sehr zuversichtlich, dass ich Änderungen an einem solchen Code vornehmen kann, ohne etwas Unerwartetes zu stolpern. Die Lösung für mich ist also entweder den Kontrollfluss vereinfacht oder die Nebenwirkungen minimiert/vereinen (mit Vereinigung, ich meine, dass in einer bestimmten Phase des Systems nur eine Art von Nebenwirkung zu vielen Dingen eine Nebenwirkung verursacht wird, nicht zwei oder drei oder a Dutzend). Ich brauche eines dieser beiden Dinge, um meinem Simpleton -Gehirn über die Richtigkeit des vorhandenen Codes und die Richtigkeit der von mir vorgestellten Änderungen sicher zu sein. Es ist ziemlich einfach, sicher zu sein, dass die Richtigkeit der Code -Einführung Nebenwirkungen einführt, wenn die Nebenwirkungen einheitlich und einfach sind, zusammen mit dem Kontrollfluss wie so:

for each pixel in an image:
    make it red

Es ist ziemlich einfach, über die Richtigkeit eines solchen Codes zu ordnen, aber vor allem, weil die Nebenwirkungen so einheitlich sind und der Kontrollfluss so einfach ist. Aber sagen wir, wir hatten einen solchen Code:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

Dann ist dies lächerlich zu vereinfachtes Pseudocode, das normalerweise weit mehr Funktionen und verschachtelte Schleifen und viel mehr Dinge beinhalten würde (Aktualisierung mehrerer Texturkarten, Knochengewichte, Auswahlzustände usw.), aber selbst der Pseudocode macht es so schwierig zu Grund für die Korrektheit aufgrund der Wechselwirkung des komplexen graphischen Kontrollflusss und der Nebenwirkungen. Eine Strategie zur Vereinfachung, die die Verarbeitung aufschieben und sich nur auf eine Art von Nebenwirkung auf einmal konzentrieren:

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

... etwas in diesem Effekt als eine Iteration der Vereinfachung. Das bedeutet, dass wir die Daten mehrmals durchlaufen, was definitiv einen Rechenkosten entspricht, aber wir können oft feststellen, dass wir einen solchen resultierenden Code leichter multithreadieren können, da die Nebenwirkungen und Kontrollströme diese gleichmäßige und einfachere Natur aufgenommen haben. Darüber hinaus kann jede Schleife cache-freundlicher gestaltet werden, als das angeschlossene Diagramm zu durchqueren und Nebenwirkungen zu verursachen (z. Verwenden von Bitmasks und FFS). Am wichtigsten ist jedoch, dass die zweite Version in Bezug auf die Richtigkeit und Änderung, ohne Fehler zu verursachen, so viel einfacher zu argumentieren. So nähere ich mich trotzdem und wende die gleiche Art von Denkweise an, um die Maschenverarbeitung dort oben zu vereinfachen wie vereinfachte Ereignishandhabungen und so weiter - mehr homogene Schleifen mit toten einfachen Kontrollströmen, die einheitliche Nebenwirkungen verursachen.

Und schließlich benötigen wir Nebenwirkungen, um irgendwann auftreten, oder wir hätten nur Funktionen, die Daten mit nirgendwohin ausgeben. Oft müssen wir etwas in einer Datei aufzeichnen, etwas auf einem Bildschirm anzeigen, die Daten über einen Socket, etwas dieser Art, übersenden, und all diese Dinge sind Nebenwirkungen. Aber wir können definitiv die Anzahl der überflüssigen Nebenwirkungen reduzieren und die Anzahl der Nebenwirkungen reduzieren, wenn die Kontrollströme sehr kompliziert sind, und ich denke, es wäre viel einfacher, Fehler zu vermeiden, wenn wir es tun.

Es ist nicht böse. Meiner Meinung nach ist es notwendig, die beiden Funktionstypen mit Nebenwirkungen und ohne zu unterscheiden. Die Funktion ohne Nebenwirkungen: - Renditen immer gleich mit denselben Argumenten, daher macht beispielsweise eine solche Funktion ohne Argumente keinen Sinn. - Das bedeutet auch, dass die Reihenfolge in den so genannten Funktionen keine Rolle spielt - ohne einen weiteren Code zu debuggen (!), Debuggierte sie möglicherweise allein (!). Und jetzt, lol, schau, was jungit macht. Eine Funktion mit Nebenwirkungen: - hat eine Art "Lecks", was automatisch hervorgehoben werden kann - es ist sehr wichtig, indem sie Fehler debuggen und durchsuchen, was im Allgemeinen durch Nebenwirkungen verursacht wird. - Jede Funktion mit Nebenwirkungen hat auch einen "Teil" von sich selbst ohne Nebenwirkungen, was auch automatisch getrennt werden kann. So böse sind diese Nebenwirkungen, welche schwierigen Fehler zu verfolgen haben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
scroll top