Warum werden Nebenwirkungen in der funktionalen Programmierung als böse angesehen?


69

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.


6
Ein Programm ohne Nebenwirkungen ist nutzlos, daher sind Nebenwirkungen weder böse noch tabu. FP setzt sich jedoch dafür ein, den Code mit Nebenwirkungen abzugrenzen, damit ein möglichst großer Teil des Codes nebenwirkungsfreie Funktionen sind. Dies wird empfohlen, da nebenwirkungsfreie Funktionen und Subsysteme einfacher zu verstehen, zu analysieren, zu testen und zu optimieren sind.
JacquesB

@JacquesB Es wäre eine gute Antwort zu erklären, warum sie leichter zu verstehen, leichter zu analysieren, leichter zu testen und leichter zu optimieren sind.
13.

Antworten:


72

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


4
Erwägen Sie vielleicht, Ihrer Antwort etwas über Parallelität hinzuzufügen?
Benjol

5
Nebenwirkungsfreie Funktionen sind einfacher zu testen und wiederzuverwenden.
LennyProgrammers

@ Lenny222: Wiederverwendung war das, was ich angedeutet habe, indem ich über Funktionszusammensetzung gesprochen habe.
Frank Shearar

@Frank: Ah, ok, auch flaches Stöbern. :)
LennyProgrammers

@ Lenny222: Ist schon ok; Es ist wahrscheinlich eine gute Sache, es zu formulieren.
Frank Shearar

23

Aus einem Artikel über funktionale Programmierung :

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



23

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.


Deshalb sind sie "so etwas wie ein Tabu" - FPLs ermutigen Sie, Nebenwirkungen zu begrenzen.
Frank Shearar

+1 für den Ansatz. Nebenwirkungen bestehen immer noch. in der Tat sind sie begrenzt
Belun

Zur Verdeutlichung habe ich nicht gesagt, warum Nebenwirkungen in der funktionalen Programmierung nicht zulässig sind oder warum Nebenwirkungen nicht erforderlich sind. Ich weiß, dass es in funktionalen Sprachen erlaubt ist und manchmal ein Muss ist. Von funktionaler Programmierung wird jedoch sehr abgeraten. Warum? Das war meine frage
Gulshan

@ Gulshan - Weil Nebenwirkungen Programme schwerer zu verstehen und zu optimieren machen.
ChaosPandion

bei haskell geht es nicht hauptsächlich darum, "nebenwirkungen zu begrenzen". Nebenwirkungen sind in der SPRACHE nicht auszudrücken. Was Funktionen readFiletun, 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.
Sara

13

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


Sehr interessanter Punkt: Es spielt keine Rolle, ob die Funktion wirklich ausgeführt wurde oder nicht . Es wäre interessant , einen Compiler zu haben, der bei entsprechenden Parametern nachfolgende Aufrufe nebenwirkungsfreier Funktionen überflüssig macht.
Noel Widmer

1
@NoelWidmer Sowas gibt es schon. Oracle's PL / SQL bietet eine deterministicKlausel für Funktionen ohne Nebenwirkungen, so dass sie nicht öfter als nötig ausgeführt werden.
user281377

Beeindruckend! Ich denke jedoch, dass Sprachen semantisch aussagekräftig sein sollten, damit der Compiler es selbst herausfinden kann, ohne ein explizites Flag angeben zu müssen (ich bin nicht sicher, was eine Klausel ist). Eine Lösung könnte darin bestehen, Parameter so festzulegen, dass sie veränderlich / unveränderlich sind, z. B. Im Allgemeinen würde dies ein starkes Typsystem erfordern, in dem Annahmen über Nebenwirkungen vom Compiler getroffen werden können. Und die Funktion muss auf Wunsch deaktiviert werden können. Opt-out statt opt-in Das ist nur meine Meinung, basierend auf dem begrenzten Wissen, das ich seit dem Lesen Ihrer Antwort habe :)
Noel Widmer

Die deterministicKlausel ist nur ein Schlüsselwort, das dem Compiler mitteilt, dass dies eine deterministische Funktion ist, vergleichbar mit dem finalSchlüsselwort in Java, das dem Compiler mitteilt, dass die Variable nicht geändert werden kann.
User281377

11

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:

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?

Ich weiß, dass fooes 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.


6

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.

Warum gelten Nebenwirkungen als böse?

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 .


1
Ada macht es sehr schwierig, Nebenwirkungen zu verursachen. Es ist zwar nicht unmöglich, aber Sie wissen genau, was Sie dann tun.
Mouviciel

@mouviciel: Ich denke, es gibt mindestens ein paar nützliche Sprachen, die die Nebenwirkungen sehr erschweren, und ich versuche, sie zu Monaden zu degradieren.
Merlyn Morgan-Graham

4

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.


+1 - "oder ein ahnungsloser Mitarbeiter"
Merlyn Morgan-Graham

1
-1 für Nebenwirkungen wie "Undichtigkeiten, die behoben werden müssen". Das Erstellen von "Nebenwirkungen" (nicht rein funktionalem Code) ist der gesamte Zweck des Schreibens eines nicht trivialen Computerprogramms.
Mason Wheeler

Dieser Kommentar kommt sechs Jahre später, aber es gibt Nebenwirkungen und dann gibt es Nebenwirkungen. Die wünschenswerte Art von Nebenwirkungen, tun I / O und so weiter, sind in der Tat notwendig , für jedes Programm, weil Sie Ihre Ergebnisse an den Benutzer geben müssen irgendwie - aber die andere Art von Nebenwirkungen, in dem der Code den Zustand ändert , ohne eine gute Grund wie E / A, sind in der Tat ein "Leck", das später behandelt werden muss. Die Grundidee ist die Trennung von Befehlen und Abfragen : Eine Funktion, die einen Wert zurückgibt, sollte keine Nebenwirkung haben.
rmunn

4

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.


Interessanterweise hat die Logikprogrammierung nicht nur keine funktionalen Nebenwirkungen. Sie können jedoch nicht einmal den Wert einer einmal zugewiesenen Variablen ändern.
Ilan

@Ilan: Dies gilt auch für einige funktionale Sprachen, und es ist ein Stil, der leicht zu übernehmen ist.
back2dos

"Die funktionale Programmierung verfolgt einen weitaus radikaleren Ansatz, bei dem der Anwendungszustand aus Sicht des Programmierers einfach unveränderlich ist. Dies ist eine nette Idee, macht die Sprache jedoch für sich unbrauchbar. Warum? Weil JEDE E / A-Operation eine Seite hat Effekte ": FP verbietet keine Nebenwirkungen, sondern schränkt sie ein, wenn dies nicht erforderlich ist. ZB (1) I / O -> Nebenwirkungen sind notwendig; (2) Berechnung einer Aggregatfunktion aus einer Folge von Werten -> Nebenwirkung (zB für Schleife mit Akkumulatorvariable) nicht erforderlich.
Giorgio

2

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.


1

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.


1
Beobachter (einschließlich der Benutzeroberfläche) sollten sich über Änderungen informieren, indem sie die von Ihrem Objekt gesendeten Nachrichten / Ereignisse abonnieren. Dies ist kein Nebeneffekt, es sei denn, das Objekt verändert den Betrachter direkt - was ein schlechtes Design wäre.
ChrisF

1
@ ChrisF Auf jeden Fall ist es eine Nebenwirkung. Die an den Beobachter übergebene Nachricht (in einer OO-Sprache höchstwahrscheinlich ein Methodenaufruf auf einer Schnittstelle) führt dazu, dass sich der Status der UI-Komponente auf dem Heap ändert (und diese Heap-Objekte für andere Teile des Programms sichtbar sind). Die UI-Komponente ist weder ein Parameter der Methode noch der Rückgabewert. In einem formalen Sinne muss eine nebenwirkungsfreie Funktion idempotent sein. Eine Benachrichtigung im MVC-Muster erfolgt nicht. Beispielsweise zeigt die Benutzeroberfläche möglicherweise eine Liste der empfangenen Nachrichten an. Ein zweimaliger Konsolenaufruf führt zu einem anderen Programmstatus.
Flamingpenguin

0

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.


0

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:

  • In Code mit veränderlichem Status können wir den Gültigkeitsbereich des Status so verwalten, dass statisch sichergestellt ist, dass er nicht außerhalb einer bestimmten Funktion ausläuft. Auf diese Weise können wir Datenmüll sammeln, ohne Referenzzähl- oder Mark-and-Sweep-Schemata zu verwenden und dennoch sicher sein, dass keine Referenzen überleben. Die gleichen Garantien sind auch nützlich für die Aufrechterhaltung datenschutzrelevanter Informationen usw. (Dies kann mit der ST-Monade in Haskell erreicht werden.)
  • Wenn Sie den gemeinsam genutzten Status in mehreren Threads ändern, können Sie die Notwendigkeit von Sperren vermeiden, indem Sie Änderungen nachverfolgen und am Ende einer Transaktion eine atomare Aktualisierung durchführen oder die Transaktion zurücksetzen und wiederholen, wenn ein anderer Thread eine widersprüchliche Änderung vorgenommen hat. Dies ist nur möglich, weil wir sicherstellen können, dass der Code keine anderen Auswirkungen hat als die Statusänderungen (auf die wir gerne verzichten können). Dies wird von der STM-Monade (Software Transactional Memory) in Haskell durchgeführt.
  • Wir können die Auswirkungen von Code nachverfolgen und ihn trivial filtern, um sicherzustellen, dass er sicher ist. So kann beispielsweise vom Benutzer eingegebener Code sicher auf einer Website ausgeführt werden

0

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.

for each pixel in an image:
    make it red

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:

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

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


-1

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,


Dies scheint nichts Wesentliches zu bieten über Punkte, die in den vorherigen 12 Antworten gemacht und erklärt wurden
Mücke
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.