Ich habe in letzter Zeit viel über funktionale Programmierung gelesen und kann das meiste davon verstehen, aber das einzige, was ich einfach nicht in den Kopf bekommen kann, ist zustandslose Codierung. Es scheint mir, dass die Vereinfachung der Programmierung durch Entfernen des veränderlichen Zustands wie das "Vereinfachen" eines Autos durch Entfernen des Armaturenbretts ist: Das fertige Produkt mag einfacher sein, aber viel Glück bei der Interaktion mit den Endbenutzern.
Nahezu jede Benutzeranwendung, die ich mir vorstellen kann, beinhaltet den Status als Kernkonzept. Wenn Sie ein Dokument (oder einen SO-Beitrag) schreiben, ändert sich der Status mit jeder neuen Eingabe. Oder wenn Sie ein Videospiel spielen, gibt es Unmengen von Statusvariablen, beginnend mit den Positionen aller Charaktere, die dazu neigen, sich ständig zu bewegen. Wie können Sie möglicherweise etwas Nützliches tun, ohne die sich ändernden Werte im Auge zu behalten?
Jedes Mal, wenn ich etwas finde, das dieses Problem behandelt, ist es in einer wirklich technischen Funktion geschrieben, die einen starken FP-Hintergrund voraussetzt, den ich nicht habe. Kennt jemand eine Möglichkeit, dies jemandem mit einem guten, soliden Verständnis der imperativen Codierung zu erklären, aber wer ist ein vollständiger n00b auf der funktionalen Seite?
EDIT: Einige der bisherigen Antworten scheinen mich von den Vorteilen unveränderlicher Werte zu überzeugen. Ich verstehe diesen Teil. Es macht vollkommen Sinn. Was ich nicht verstehe, ist, wie Sie Werte verfolgen können, die sich ändern müssen und sich ständig ändern, ohne veränderbare Variablen.
quelle
Antworten:
Wenn Sie interessiert sind, finden Sie hier eine Reihe von Artikeln, die die Spielprogrammierung mit Erlang beschreiben.
Diese Antwort wird Ihnen wahrscheinlich nicht gefallen, aber Sie erhalten erst dann ein funktionierendes Programm, wenn Sie es verwenden. Ich kann Codebeispiele posten und sagen : „Hier, nicht wahr sehen “ - aber wenn Sie die Syntax und die zugrunde liegenden Prinzipien nicht verstehen, dann die Augen glasig gerade. Aus Ihrer Sicht sieht es so aus, als würde ich das Gleiche tun wie eine imperative Sprache, aber nur alle Arten von Grenzen setzen, um das Programmieren gezielt zu erschweren. Meiner Ansicht nach erleben Sie gerade das Blub-Paradoxon .
Anfangs war ich skeptisch, aber ich bin vor ein paar Jahren in den funktionalen Programmierzug gestiegen und habe mich in ihn verliebt. Der Trick bei der funktionalen Programmierung besteht darin, Muster und bestimmte Variablenzuweisungen zu erkennen und den imperativen Zustand auf den Stapel zu verschieben. Eine for-Schleife wird beispielsweise zur Rekursion:
Es ist nicht sehr hübsch, aber wir haben den gleichen Effekt ohne Mutation. Natürlich vermeiden wir, wo immer möglich, das Schleifen ganz und abstrahieren es einfach weg:
Die Seq.iter-Methode zählt die Sammlung auf und ruft die anonyme Funktion für jedes Element auf. Sehr praktisch :)
Ich weiß, das Drucken von Zahlen ist nicht gerade beeindruckend. Bei Spielen können wir jedoch denselben Ansatz verwenden: Halten Sie den gesamten Status im Stapel und erstellen Sie ein neues Objekt mit unseren Änderungen im rekursiven Aufruf. Auf diese Weise ist jeder Frame eine zustandslose Momentaufnahme des Spiels, wobei jeder Frame einfach ein brandneues Objekt mit den gewünschten Änderungen aller zustandslosen Objekte erstellt, die aktualisiert werden müssen. Der Pseudocode hierfür könnte sein:
Die imperative und die funktionale Version sind identisch, aber die funktionale Version verwendet eindeutig keinen veränderlichen Zustand. Der Funktionscode hält den gesamten Status auf dem Stapel - das Schöne an diesem Ansatz ist, dass das Debuggen einfach ist, wenn etwas schief geht. Sie benötigen lediglich eine Stapelverfolgung.
Dies lässt sich auf eine beliebige Anzahl von Objekten im Spiel skalieren, da alle Objekte (oder Sammlungen verwandter Objekte) in einem eigenen Thread gerendert werden können.
In funktionalen Sprachen geben wir einfach ein neues Objekt mit den gewünschten Änderungen zurück, anstatt den Status von Objekten zu ändern. Es ist effizienter als es klingt. Datenstrukturen sind beispielsweise sehr einfach als unveränderliche Datenstrukturen darzustellen. Stapel sind beispielsweise notorisch einfach zu implementieren:
Der obige Code erstellt zwei unveränderliche Listen, hängt sie zusammen, um eine neue Liste zu erstellen, und hängt die Ergebnisse an. In der gesamten Anwendung wird kein veränderlicher Status verwendet. Es sieht etwas sperrig aus, aber das liegt nur daran, dass C # eine ausführliche Sprache ist. Hier ist das entsprechende Programm in F #:
Keine Änderung erforderlich, um Listen zu erstellen und zu bearbeiten. Nahezu alle Datenstrukturen können problemlos in ihre Funktionsäquivalente umgewandelt werden. Ich habe hier eine Seite geschrieben , die unveränderliche Implementierungen von Stapeln, Warteschlangen, linken Haufen, rot-schwarzen Bäumen und faulen Listen enthält. Kein einziger Codeausschnitt enthält einen veränderlichen Status. Um einen Baum zu "mutieren", erstelle ich einen brandneuen mit einem neuen Knoten, den ich möchte. Dies ist sehr effizient, da ich nicht von jedem Knoten im Baum eine Kopie erstellen muss, sondern die alten in meinem neuen wiederverwenden kann Baum.
Anhand eines aussagekräftigeren Beispiels habe ich auch diesen SQL-Parser geschrieben, der völlig zustandslos ist (oder zumindest mein Code ist zustandslos, ich weiß nicht, ob die zugrunde liegende Lexing-Bibliothek zustandslos ist).
Staatenloses Programmieren ist genauso ausdrucksstark und leistungsfähig wie zustandsloses Programmieren. Es erfordert nur ein wenig Übung, um sich darin zu üben, staatenlos zu denken. Natürlich scheint "zustandslose Programmierung, wenn möglich, zustandsbehaftete Programmierung, wo nötig" das Motto der unreinsten funktionalen Sprachen zu sein. Es schadet nicht, auf veränderliche Elemente zurückzugreifen, wenn der funktionale Ansatz nicht so sauber oder effizient ist.
quelle
Kurze Antwort: Sie können nicht.
Was ist dann die Aufregung um Unveränderlichkeit?
Wenn Sie sich mit der imperativen Sprache auskennen, wissen Sie, dass "Globale schlecht sind". Warum? Weil sie einige sehr schwer zu entwirrende Abhängigkeiten in Ihren Code einführen (oder einführen können). Und Abhängigkeiten sind nicht gut; Sie möchten, dass Ihr Code modular aufgebaut ist . Programmteile beeinflussen andere Teile nicht so wenig wie möglich. Und FP bringt Sie zu den heiligen Gral der Modularität: keine Nebenwirkungen überhaupt . Sie haben nur Ihr f (x) = y. Gib x rein, hol y raus. Keine Änderungen an x oder irgendetwas anderem. Mit FP hören Sie auf, über den Zustand nachzudenken, und beginnen, in Werten zu denken. Alle Ihre Funktionen empfangen einfach Werte und erzeugen neue Werte.
Dies hat mehrere Vorteile.
Zunächst einmal bedeuten keine Nebenwirkungen einfachere Programme, über die man leichter nachdenken kann. Keine Sorge, dass die Einführung eines neuen Programmteils einen vorhandenen, funktionierenden Teil stören und zum Absturz bringen wird.
Zweitens macht dies das Programm trivial parallelisierbar (eine effiziente Parallelisierung ist eine andere Sache).
Drittens gibt es einige mögliche Leistungsvorteile. Angenommen, Sie haben eine Funktion:
Jetzt geben Sie einen Wert von 3 ein und Sie erhalten einen Wert von 6 aus. Jedes Mal. Aber das können Sie auch unbedingt tun, oder? Ja. Das Problem ist jedoch, dass Sie unbedingt noch mehr tun können . Ich kann:
aber ich könnte es auch tun
Der imperative Compiler weiß nicht, ob ich Nebenwirkungen haben werde oder nicht, was die Optimierung schwieriger macht (dh Double 2 muss nicht jedes Mal 4 sein). Der Funktionale weiß, dass ich es nicht tun werde - daher kann er jedes Mal optimieren, wenn er "double 2" sieht.
Obwohl das Erstellen neuer Werte jedes Mal für komplexe Wertetypen in Bezug auf den Computerspeicher unglaublich verschwenderisch erscheint, muss dies nicht der Fall sein. Denn wenn Sie f (x) = y haben und die Werte x und y "meistens gleich" sind (z. B. Bäume, die sich nur in wenigen Blättern unterscheiden), können x und y Teile des Speichers gemeinsam nutzen - da keiner von beiden mutiert .
Also, wenn diese unveränderliche Sache so großartig ist, warum habe ich geantwortet, dass man ohne veränderlichen Zustand nichts Nützliches tun kann? Nun, ohne Veränderbarkeit wäre Ihr gesamtes Programm eine riesige f (x) = y-Funktion. Und das Gleiche gilt für alle Teile Ihres Programms: nur Funktionen und Funktionen im "reinen" Sinne. Wie gesagt, dies bedeutet jedes Mal f (x) = y . So müsste beispielsweise readFile ("myFile.txt") jedes Mal den gleichen Zeichenfolgenwert zurückgeben. Nicht allzu nützlich.
Daher bietet jedes FP ein Mittel zum Mutieren des Zustands. "Reine" funktionale Sprachen (z. B. Haskell) tun dies mit etwas beängstigenden Konzepten wie Monaden, während "unreine" (z. B. ML) dies direkt zulassen.
Und natürlich kommen funktionale Sprachen mit einer Vielzahl anderer Extras, die die Programmierung effizienter machen, wie erstklassige Funktionen usw.
quelle
int double(x){ return x * (++y); }
da der aktuelle immer noch 4 sein wird, obwohl er immer noch eine unangekündigte Nebenwirkung hat, während er++y
6Beachten Sie, dass die Aussage, dass funktionale Programmierung keinen Status hat, etwas irreführend ist und die Ursache für die Verwirrung sein kann. Es hat definitiv keinen "veränderlichen Zustand", aber es kann immer noch Werte haben, die manipuliert werden. Sie können einfach nicht direkt geändert werden (z. B. müssen Sie aus den alten Werten neue Werte erstellen).
Dies ist eine grobe Vereinfachung, aber stellen Sie sich vor, Sie hätten eine OO-Sprache, in der alle Eigenschaften von Klassen nur einmal im Konstruktor festgelegt werden. Alle Methoden sind statische Funktionen. Sie können so ziemlich jede Berechnung durchführen, indem Methoden Objekte verwenden, die alle Werte enthalten, die sie für ihre Berechnungen benötigen, und dann neue Objekte mit dem Ergebnis zurückgeben (möglicherweise sogar eine neue Instanz desselben Objekts).
Es mag "schwierig" sein, vorhandenen Code in dieses Paradigma zu übersetzen, aber das liegt daran, dass es wirklich eine völlig andere Art des Denkens über Code erfordert. Als Nebeneffekt erhalten Sie jedoch in den meisten Fällen viele kostenlose Parallelitätsmöglichkeiten.
Nachtrag: (In Bezug auf Ihre Bearbeitung, wie
Sie die Werte verfolgen können, die geändert werden müssen) Sie würden natürlich in einer unveränderlichen Datenstruktur gespeichert ...
Dies ist keine vorgeschlagene "Lösung", aber der einfachste Weg, um zu sehen, dass dies immer funktioniert, besteht darin, dass Sie diese unveränderlichen Werte in einer kartenähnlichen Struktur (Wörterbuch / Hashtabelle) speichern können, die mit einem "Variablennamen" versehen ist.
Natürlich würden Sie in praktischen Lösungen einen vernünftigeren Ansatz verwenden, aber dies zeigt, dass Sie im schlimmsten Fall, wenn nichts anderes funktionieren würde, den veränderlichen Zustand mit einer solchen Karte "simulieren" könnten, die Sie durch Ihren Aufrufbaum tragen.
quelle
Ich denke, es gibt ein leichtes Missverständnis. Reine Funktionsprogramme haben Status. Der Unterschied besteht darin, wie dieser Zustand modelliert wird. Bei der reinen Funktionsprogrammierung wird der Status durch Funktionen manipuliert, die einen bestimmten Status annehmen und den nächsten Status zurückgeben. Die Sequenzierung durch Zustände wird dann erreicht, indem der Zustand durch eine Folge von reinen Funktionen geleitet wird.
Sogar der globale veränderbare Zustand kann auf diese Weise modelliert werden. In Haskell zum Beispiel ist ein Programm eine Funktion von einer Welt zur anderen. Das heißt, Sie übergeben das gesamte Universum und das Programm gibt ein neues Universum zurück. In der Praxis müssen Sie jedoch nur die Teile des Universums übergeben, an denen Ihr Programm tatsächlich interessiert ist. Und Programme geben tatsächlich eine Folge von Aktionen zurück , die als Anweisungen für die Betriebsumgebung dienen, in der das Programm ausgeführt wird.
Sie wollten dies anhand der imperativen Programmierung erklären. OK, schauen wir uns eine wirklich einfache imperative Programmierung in einer funktionalen Sprache an.
Betrachten Sie diesen Code:
Ziemlich zwingender Standard-Imperativcode. Macht nichts Interessantes, aber das ist zur Veranschaulichung in Ordnung. Ich denke, Sie werden zustimmen, dass es hier um einen Staat geht. Der Wert der Variablen x ändert sich mit der Zeit. Lassen Sie uns nun die Notation leicht ändern, indem wir eine neue Syntax erfinden:
Setzen Sie Klammern, um klarer zu machen, was dies bedeutet:
Sie sehen, der Zustand wird durch eine Folge von reinen Ausdrücken modelliert, die die freien Variablen der folgenden Ausdrücke binden.
Sie werden feststellen, dass dieses Muster jede Art von Zustand modellieren kann, sogar E / A.
quelle
So schreiben Sie Code ohne veränderlichen Status : Anstatt den sich ändernden Status in veränderbare Variablen umzuwandeln, fügen Sie ihn in die Parameter von Funktionen ein. Und anstatt Schleifen zu schreiben, schreiben Sie rekursive Funktionen. So zum Beispiel dieser zwingende Code:
wird dieser Funktionscode (Schema-ähnliche Syntax):
oder dieser Haskellish Code
In Bezug auf , warum funktionale Programmierer mögen , dies zu tun (was man nicht fragen), desto mehr Teile des Programms sind staatenlos, desto mehr Möglichkeiten gibt es Stücke zusammen zu stellen , ohne etwas Pause . Die Kraft des staatenlosen Paradigmas liegt nicht in der Staatenlosigkeit (oder Reinheit) an sich , sondern in der Fähigkeit, leistungsstarke, wiederverwendbare Funktionen zu schreiben und zu kombinieren.
Ein gutes Tutorial mit vielen Beispielen finden Sie in John Hughes 'Artikel Why Functional Programming Matters .
quelle
Es sind nur verschiedene Arten, dasselbe zu tun.
Stellen Sie sich ein einfaches Beispiel vor, wie das Hinzufügen der Zahlen 3, 5 und 10. Stellen Sie sich vor, Sie ändern den Wert von 3, indem Sie 5 hinzufügen, dann 10 zu dieser "3" hinzufügen und dann den aktuellen Wert von "ausgeben. 3 "(18). Dies scheint offensichtlich lächerlich, aber es ist im Wesentlichen die Art und Weise, wie staatsbasierte imperative Programmierung häufig durchgeführt wird. In der Tat können Sie viele verschiedene "3" haben, die den Wert 3 haben, aber unterschiedlich sind. All dies scheint seltsam, weil wir so tief in der enorm vernünftigen Idee verwurzelt sind, dass die Zahlen unveränderlich sind.
Denken Sie nun daran, 3, 5 und 10 hinzuzufügen, wenn Sie die Werte als unveränderlich betrachten. Sie addieren 3 und 5, um einen weiteren Wert zu erzeugen, 8, und addieren 10 zu diesem Wert, um einen weiteren Wert zu erzeugen, 18.
Dies sind äquivalente Methoden, um dasselbe zu tun. Alle erforderlichen Informationen sind in beiden Methoden vorhanden, jedoch in unterschiedlicher Form. In einem existieren die Informationen als Zustand und in den Regeln zum Ändern des Zustands. Im anderen Fall liegen die Informationen in unveränderlichen Daten und Funktionsdefinitionen vor.
quelle
Ich komme zu spät zur Diskussion, aber ich wollte ein paar Punkte für Leute hinzufügen, die mit funktionaler Programmierung zu kämpfen haben.
Zuerst der imperative Weg (im Pseudocode)
Nun der funktionale Weg (im Pseudocode). Ich stütze mich stark auf den ternären Operator, weil ich möchte, dass Menschen mit zwingendem Hintergrund diesen Code tatsächlich lesen können. Wenn Sie den ternären Operator also nicht häufig verwenden (ich habe ihn in meinen zwingenden Tagen immer vermieden), funktioniert er folgendermaßen.
Sie können den ternären Ausdruck verketten, indem Sie anstelle des falschen Ausdrucks einen neuen ternären Ausdruck einfügen
In diesem Sinne ist hier die funktionale Version.
Dies ist ein triviales Beispiel. Wenn dies Menschen in einer Spielwelt bewegen würde, müssten Sie Nebenwirkungen wie das Zeichnen der aktuellen Position des Objekts auf dem Bildschirm und eine gewisse Verzögerung bei jedem Aufruf einführen, je nachdem, wie schnell sich das Objekt bewegt. Aber Sie würden immer noch keinen veränderlichen Zustand brauchen.
Die Lektion ist, dass funktionale Sprachen den Status "mutieren", indem sie die Funktion mit verschiedenen Parametern aufrufen. Natürlich mutiert dies keine Variablen, aber so erhalten Sie einen ähnlichen Effekt. Dies bedeutet, dass Sie sich daran gewöhnen müssen, rekursiv zu denken, wenn Sie funktionale Programmierung durchführen möchten.
Rekursives Denken zu lernen ist nicht schwer, erfordert jedoch sowohl Übung als auch ein Toolkit. Dieser kleine Abschnitt in diesem "Java lernen" -Buch, in dem die Rekursion zur Berechnung der Fakultät verwendet wurde, schneidet ihn nicht ab. Sie benötigen ein Toolkit mit Fähigkeiten wie dem Erstellen iterativer Prozesse aus der Rekursion (aus diesem Grund ist die Schwanzrekursion für die funktionale Sprache unerlässlich), Fortsetzungen, Invarianten usw. Sie würden keine OO-Programmierung durchführen, ohne etwas über Zugriffsmodifikatoren, Schnittstellen usw. zu lernen zur funktionalen Programmierung.
Meine Empfehlung ist, den kleinen Schemer zu machen (beachten Sie, dass ich "tun" und nicht "lesen" sage) und dann alle Übungen in SICP zu machen. Wenn Sie fertig sind, haben Sie ein anderes Gehirn als zu Beginn.
quelle
Es ist in der Tat ziemlich einfach, etwas zu haben, das wie ein veränderlicher Zustand aussieht, selbst in Sprachen ohne veränderlichen Zustand.
Betrachten Sie eine Funktion mit Typ
s -> (a, s)
. Das Übersetzen von Haskell Syntax, bedeutet dies eine Funktion , die einen Parameter vom Typ „nimmts
“ und gibt ein Paar von Werten, dem Typs „a
“ und „s
“. Wenn diess
der Typ unseres Status ist, nimmt diese Funktion einen Status an und gibt einen neuen Status und möglicherweise einen Wert zurück (Sie können jederzeit "unit" aka()
, wasvoid
in C / C ++ "a
" entspricht , als " " zurückgeben). Art). Wenn Sie mehrere Funktionsaufrufe mit solchen Typen verketten (den Status von einer Funktion zurückgeben und an die nächste übergeben), haben Sie einen "veränderlichen" Status (tatsächlich erstellen Sie in jeder Funktion einen neuen Status und geben den alten auf ).Es ist möglicherweise einfacher zu verstehen, wenn Sie sich den veränderlichen Zustand als den "Raum" vorstellen, in dem Ihr Programm ausgeführt wird, und dann an die Zeitdimension denken. Zum Zeitpunkt t1 befindet sich der "Raum" in einem bestimmten Zustand (zum Beispiel hat ein Speicherplatz den Wert 5). Zu einem späteren Zeitpunkt t2 befindet es sich in einem anderen Zustand (zum Beispiel hat dieser Speicherort jetzt den Wert 10). Jede dieser Zeit- "Scheiben" ist ein Zustand und unveränderlich (Sie können nicht in der Zeit zurückgehen, um sie zu ändern). Unter diesem Gesichtspunkt sind Sie also von der vollen Raumzeit mit einem Zeitpfeil (Ihrem veränderlichen Zustand) zu einer Reihe von Raumzeitscheiben (mehreren unveränderlichen Zuständen) übergegangen, und Ihr Programm behandelt jede Schicht nur als Wert und berechnet jede von ihnen als eine Funktion auf die vorherige angewendet.
OK, vielleicht war das nicht einfacher zu verstehen :-)
Es erscheint möglicherweise unzulänglich, den gesamten Programmstatus explizit als Wert darzustellen, der nur erstellt werden muss, um im nächsten Moment verworfen zu werden (unmittelbar nachdem ein neuer erstellt wurde). Für einige Algorithmen mag es natürlich sein, aber wenn dies nicht der Fall ist, gibt es einen anderen Trick. Anstelle eines realen Zustands können Sie einen gefälschten Zustand verwenden, der nichts anderes als ein Marker ist (nennen wir den Typ dieses gefälschten Zustands
State#
). Dieser gefälschte Zustand existiert aus Sicht der Sprache und wird wie jeder andere Wert übergeben, aber der Compiler lässt ihn beim Generieren des Maschinencodes vollständig weg. Es dient nur dazu, die Reihenfolge der Ausführung zu markieren.Angenommen, der Compiler bietet uns die folgenden Funktionen:
Wenn Sie aus diesen Haskell-ähnlichen Deklarationen übersetzen,
readRef
erhalten Sie etwas, das einem Zeiger oder einem Handle auf einen Wert vom Typ "a
" und den falschen Status ähnelt , und geben den Wert vom Typ "a
" zurück, auf den der erste Parameter und ein neuer gefälschter Status zeigen.writeRef
ist ähnlich, ändert jedoch den Wert, auf den stattdessen verwiesen wird.Wenn Sie
readRef
den von zurückgegebenen falschen Status aufrufen und dann übergebenwriteRef
(möglicherweise bei anderen Aufrufen nicht verwandter Funktionen in der Mitte; diese Statuswerte bilden eine "Kette" von Funktionsaufrufen), wird der geschriebene Wert zurückgegeben. Sie könnenwriteRef
erneut mit demselben Zeiger / Handle aufrufen, und es wird an denselben Speicherort geschrieben. Da jedoch konzeptionell ein neuer (gefälschter) Status zurückgegeben wird, ist der (gefälschte) Status immer noch unveränderlich (ein neuer wurde "erstellt" "). Der Compiler ruft die Funktionen in der Reihenfolge auf, in der er sie aufrufen müsste, wenn eine reale Statusvariable berechnet werden müsste, aber der einzige Status, der vorhanden ist, ist der vollständige (veränderbare) Status der realen Hardware.(Diejenigen , die Haskell wissen, merke ich , die Dinge vereinfacht viel und ommited einige wichtige Details. Für diejenigen , die wollen mehr Details zu sehen, zu sehen ,
Control.Monad.State
von dermtl
und an denST s
undIO
(akaST RealWorld
) Monaden.)Sie fragen sich vielleicht, warum Sie dies so umständlich tun (anstatt einfach einen veränderlichen Zustand in der Sprache zu haben). Der eigentliche Vorteil ist, dass Sie den Status Ihres Programms geändert haben . Was vorher implizit war (Ihr Programmstatus war global und erlaubte Dinge wie Fernwirkung ), ist jetzt explizit. Funktionen, die den Status nicht empfangen und zurückgeben, können ihn nicht ändern oder von ihm beeinflusst werden. Sie sind "rein". Noch besser ist, dass Sie separate Status-Threads haben können und mit ein wenig Typ-Magie eine imperative Berechnung in eine reine einbetten können, ohne sie unrein zu machen (die
ST
Monade in Haskell wird normalerweise für diesen Trick verwendet; DasState#
oben erwähnte ist in der Tat GHCState# s
, das von seiner Implementierung desST
und verwendet wirdIO
Monaden).quelle
Funktionale Programmierung vermeidet Zustand und betontFunktionalität. Es gibt nie einen Staat, obwohl der Staat tatsächlich etwas ist, das unveränderlich ist oder in die Architektur dessen eingebettet ist, mit dem Sie arbeiten. Betrachten Sie den Unterschied zwischen einem statischen Webserver, der nur Dateien aus dem Dateisystem lädt, und einem Programm, das einen Rubik-Cube implementiert. Ersteres wird in Form von Funktionen implementiert, mit denen eine Anforderung in eine Dateipfadanforderung in eine Antwort aus dem Inhalt dieser Datei umgewandelt werden soll. Über einen winzigen Teil der Konfiguration hinaus wird praktisch kein Status benötigt (der Dateisystem-Status liegt wirklich außerhalb des Programmbereichs. Das Programm funktioniert auf die gleiche Weise, unabhängig davon, in welchem Status sich die Dateien befinden). In letzterem Fall müssen Sie jedoch den Cube und Ihre Programmimplementierung modellieren, wie Operationen an diesem Cube seinen Status ändern.
quelle
Denken Sie zusätzlich zu den großartigen Antworten, die andere geben, an die Klassen
Integer
undString
an Java. Instanzen dieser Klassen sind unveränderlich, aber das macht die Klassen nicht unbrauchbar, nur weil ihre Instanzen nicht geändert werden können. Die Unveränderlichkeit gibt Ihnen etwas Sicherheit. Sie wissen, wenn Sie eine String- oder Integer-Instanz als Schlüssel für a verwendenMap
, kann der Schlüssel nicht geändert werden. Vergleichen Sie dies mit derDate
Klasse in Java:Sie haben stillschweigend einen Schlüssel in Ihrer Karte geändert! Die Arbeit mit unveränderlichen Objekten, wie beispielsweise in der funktionalen Programmierung, ist viel sauberer. Es ist einfacher zu überlegen, welche Nebenwirkungen auftreten - keine! Dies bedeutet, dass es für den Programmierer und auch für den Optimierer einfacher ist.
quelle
Für hoch interaktive Anwendungen wie Spiele ist Functional Reactive Programming Ihr Freund: Wenn Sie die Eigenschaften der Spielwelt als zeitlich variierende Werte (und / oder Ereignisströme) formulieren können , sind Sie bereit! Diese Formeln sind manchmal noch natürlicher und aufschlussreicher als das Mutieren eines Zustands, z. B. für einen sich bewegenden Ball können Sie direkt das bekannte Gesetz x = v * t verwenden . Und was ist besser, geschrieben die Spielregeln so compose besser als objektorientierte Abstraktionen. In diesem Fall kann die Geschwindigkeit des Balls beispielsweise auch ein zeitlich variierender Wert sein, der vom Ereignisstrom abhängt, der aus den Kollisionen des Balls besteht. Weitere konkrete Überlegungen zum Design finden Sie unter Erstellen von Spielen in Elm .
quelle
Mit etwas Kreativität und Mustervergleich wurden zustandslose Spiele erstellt:
sowie rollierende Demos:
und Visualisierungen:
quelle
So würde FORTRAN ohne GEMEINSAME Blöcke funktionieren: Sie würden Methoden schreiben, die die von Ihnen übergebenen Werte und lokale Variablen enthalten. Das ist es.
Die objektorientierte Programmierung brachte uns Zustand und Verhalten zusammen, aber es war eine neue Idee, als ich sie 1994 zum ersten Mal in C ++ entdeckte.
Meine Güte, ich war ein funktionaler Programmierer, als ich Maschinenbauingenieur war und ich wusste es nicht!
quelle
Denken Sie daran: Funktionssprachen sind Turing vollständig. Daher kann jede nützliche Aufgabe, die Sie in einer imperitiven Sprache ausführen würden, in einer funktionalen Sprache ausgeführt werden. Letztendlich denke ich jedoch, dass von einem hybriden Ansatz etwas zu sagen ist. Sprachen wie F # und Clojure (und ich bin sicher, andere) fördern das zustandslose Design, ermöglichen jedoch bei Bedarf die Veränderbarkeit.
quelle
Sie können keine reine funktionale Sprache haben, die nützlich ist. Es wird immer ein Maß an Veränderlichkeit geben, mit dem Sie sich befassen müssen. IO ist ein Beispiel.
Stellen Sie sich funktionale Sprachen als ein weiteres Werkzeug vor, das Sie verwenden. Es ist gut für bestimmte Dinge, aber nicht für andere. Das von Ihnen angegebene Spielbeispiel ist möglicherweise nicht der beste Weg, eine funktionale Sprache zu verwenden. Zumindest hat der Bildschirm einen veränderlichen Status, gegen den Sie mit FP nichts ändern können. Die Art und Weise, wie Sie über Probleme denken und welche Probleme Sie mit FP lösen, unterscheidet sich von denen, die Sie bei der imperativen Programmierung gewohnt sind.
quelle
Durch viel Rekursion.
Tic Tac Toe in F # (Eine funktionale Sprache.)
quelle
Das ist sehr einfach. Sie können in der Funktionsprogrammierung so viele Variablen verwenden, wie Sie möchten ... aber nur, wenn es sich um lokale Variablen handelt (die in Funktionen enthalten sind). Wickeln Sie also einfach Ihren Code in Funktionen ein, übergeben Sie Werte zwischen diesen Funktionen hin und her (als übergebene Parameter und zurückgegebene Werte) ... und das ist alles, was dazu gehört!
Hier ist ein Beispiel:
quelle