Wie erstelle ich ein System mit den folgenden Funktionen :
- Verwendung reiner Funktionen mit unveränderlichen Objekten.
- Übergeben Sie nur Daten an eine Funktion, die die Funktion benötigt, nicht mehr (dh kein großes Objekt für den Anwendungsstatus).
- Vermeiden Sie zu viele Argumente für Funktionen.
- Vermeiden Sie es, neue Objekte nur zum Packen und Entpacken von Parametern in Funktionen zu erstellen, um zu vermeiden, dass zu viele Parameter an Funktionen übergeben werden. Wenn ich mehrere Elemente in eine Funktion als ein einzelnes Objekt packen möchte, möchte ich, dass dieses Objekt Eigentümer dieser Daten ist und nicht vorübergehend erstellt wird
Mir scheint, die Staatsmonade verstößt gegen Regel Nr. 2, obwohl es nicht offensichtlich ist, weil sie durch die Monade eingewebt ist.
Ich habe das Gefühl, dass ich Objektive irgendwie benutzen muss, aber für nicht-funktionale Sprachen wird sehr wenig darüber geschrieben.
Hintergrund
Als Übung konvertiere ich eine meiner vorhandenen Anwendungen von einem objektorientierten Stil in einen funktionalen Stil. Das erste, was ich versuche, ist, so viel wie möglich aus dem inneren Kern der Anwendung herauszuholen.
Eine Sache, die ich gehört habe, ist, wie man "Staat" in einer rein funktionalen Sprache verwaltet, und ich glaube, dass dies von staatlichen Monaden getan wird, ist, dass man logischerweise eine reine Funktion nennt, "den Staat übergeben" world as it is ", wenn die Funktion zurückkehrt, kehrt sie zu Ihnen zurück, wie sich der Zustand der Welt geändert hat.
Um zu veranschaulichen, wie Sie eine "Hallo-Welt" auf rein funktionale Weise erstellen können, übergeben Sie in Ihrem Programm den Status des Bildschirms und erhalten den Status des Bildschirms mit der Aufschrift "Hallo-Welt" zurück. Technisch gesehen rufen Sie also eine reine Funktion auf, und es gibt keine Nebenwirkungen.
Darauf basierend habe ich meine Anwendung durchgesehen und: 1. Zuerst habe ich meinen gesamten Anwendungsstatus in ein einziges globales Objekt (GameState) gestellt. 2. Zweitens habe ich GameState unveränderlich gemacht. Du kannst es nicht ändern. Wenn Sie eine Änderung benötigen, müssen Sie eine neue erstellen. Ich habe dazu einen Copy-Konstruktor hinzugefügt, der optional ein oder mehrere Felder übernimmt, die sich geändert haben. 3. Zu jeder Anwendung übergebe ich den GameState als Parameter. Innerhalb der Funktion erstellt es, nachdem es getan hat, was es tun wird, einen neuen GameState und gibt ihn zurück.
Wie ich einen reinen Funktionskern und eine Schleife an der Außenseite habe, die diesen GameState in die Haupt-Workflow-Schleife der Anwendung einspeist.
Meine Frage:
Mein Problem ist nun, dass der GameState ungefähr 15 verschiedene unveränderliche Objekte hat. Viele der Funktionen auf der niedrigsten Ebene wirken sich nur auf wenige dieser Objekte aus, z. Nehmen wir also an, ich habe eine Funktion, die die Punktzahl berechnet. Heute wird der GameState an diese Funktion übergeben, die die Punktzahl ändert, indem ein neuer GameState mit einer neuen Punktzahl erstellt wird.
Daran scheint etwas nicht zu stimmen. Die Funktion benötigt nicht das gesamte GameState. Es wird nur das Score-Objekt benötigt. Also habe ich es aktualisiert, um die Partitur zu übergeben und nur die Partitur zurückzugeben.
Das schien Sinn zu machen, also ging ich mit anderen Funktionen weiter. Für einige Funktionen müsste ich 2, 3 oder 4 Parameter vom GameState übergeben, aber da ich das Muster bis zum äußeren Kern der Anwendung verwendet habe, übergebe ich immer mehr den Anwendungsstatus. Wie oben in der Workflow-Schleife würde ich eine Methode aufrufen, die eine Methode usw. aufruft, bis zu dem Punkt, an dem die Punktzahl berechnet wird. Das bedeutet, dass die aktuelle Punktzahl durch alle diese Ebenen geleitet wird, nur weil eine Funktion ganz unten die Punktzahl berechnet.
So, jetzt habe ich Funktionen mit manchmal Dutzenden von Parametern. Ich könnte diese Parameter in ein Objekt einfügen, um die Anzahl der Parameter zu verringern, aber dann möchte ich, dass diese Klasse der Master-Speicherort des Status der Statusanwendung ist und nicht ein Objekt, das zum Zeitpunkt des Aufrufs einfach erstellt wurde, um ein Übergeben zu vermeiden in mehreren Parametern, und packen Sie sie dann aus.
Jetzt frage ich mich, ob das Problem ist, dass meine Funktionen zu tief verschachtelt sind. Dies ist das Ergebnis des Wunsches nach kleinen Funktionen. Deshalb überarbeite ich, wenn eine Funktion zu groß wird, und teile sie in mehrere kleinere Funktionen auf. Dies führt jedoch zu einer tieferen Hierarchie, und alles, was in die inneren Funktionen übergeht, muss an die äußere Funktion übergeben werden, auch wenn die äußere Funktion diese Objekte nicht direkt bearbeitet.
Es schien, als würde dieses Problem vermieden, indem man den GameState einfach auf dem Weg dahin weitergab. Aber ich komme auf das ursprüngliche Problem zurück, mehr Informationen an eine Funktion zu übergeben, als die Funktion benötigt.
quelle
Antworten:
Ich bin mir nicht sicher, ob es eine gute Lösung gibt. Dies mag eine Antwort sein oder nicht, aber es ist viel zu lang für einen Kommentar. Ich habe etwas Ähnliches gemacht und die folgenden Tricks haben geholfen:
GameState
Teilen Sie die Hierarchie so auf, dass Sie 3-5 kleinere Teile anstelle von 15 erhalten.Ich glaube nicht. Das Umgestalten in kleine Funktionen ist richtig, aber vielleicht könnten Sie sie besser umgruppieren. Manchmal ist es nicht möglich, manchmal braucht es nur einen zweiten (oder dritten) Blick auf das Problem.
Vergleichen Sie Ihr Design mit dem veränderlichen. Gibt es Dinge, die sich durch das Umschreiben verschlimmert haben? Wenn ja, können Sie sie nicht auf dieselbe Weise verbessern, wie Sie es ursprünglich getan haben?
quelle
Ich kann nicht mit C # sprechen, aber in Haskell würden Sie am Ende den ganzen Staat herumreichen. Sie können dies entweder explizit oder mit einer staatlichen Monade tun. Eine Sache, die Sie tun können, um das Problem zu lösen, dass Funktionen mehr Informationen erhalten, als sie benötigen, ist die Verwendung von Has-Typenklassen. (Wenn Sie nicht vertraut sind, ähneln Haskell-Typklassen C # -Schnittstellen.) Für jedes Element E des Status können Sie eine Typklasse HasE definieren, für die eine Funktion getE erforderlich ist, die den Wert E zurückgibt. Die Statusmonade kann dann sein machte eine Instanz von all diesen Typenklassen. Anstatt Ihre staatliche Monade explizit zu fordern, benötigen Sie in Ihren eigentlichen Funktionen jede Monade, die zu den Has-Typenklassen gehört, für die Elemente, die Sie benötigen. das schränkt ein, was die Funktion mit der Monade tun kann, die sie verwendet. Weitere Informationen zu diesem Ansatz finden Sie unter Michael Snoymanauf dem ReaderT-Entwurfsmuster posten .
Sie könnten wahrscheinlich so etwas in C # replizieren, je nachdem, wie Sie den Status definieren, der weitergegeben wird. Wenn Sie so etwas haben
Sie könnten Schnittstellen definieren
IHasMyInt
undIHasMyString
mit MethodenGetMyInt
undGetMyString
jeweils. Die Staatsklasse sieht dann so aus:In diesem Fall benötigen Ihre Methoden möglicherweise IHasMyInt, IHasMyString oder den gesamten MyState.
Sie können dann die where-Einschränkung für die Funktionsdefinition verwenden, damit Sie das Statusobjekt übergeben können, aber es kann nur zu string und int gelangen, nicht zu double.
quelle
Ich denke, Sie sollten sich über Redux oder Elm und deren Umgang mit dieser Frage informieren.
Grundsätzlich haben Sie eine reine Funktion, die den gesamten Status und die vom Benutzer ausgeführte Aktion übernimmt und den neuen Status zurückgibt.
Diese Funktion ruft dann andere reine Funktionen auf, von denen jede einen bestimmten Teil des Zustands behandelt. Abhängig von der Aktion können viele dieser Funktionen nichts anderes tun, als den ursprünglichen Zustand unverändert wiederherzustellen.
Um mehr zu erfahren, Google the Elm Architecture oder Redux.js.org.
quelle
Ich denke, Sie versuchen, eine objektorientierte Sprache so zu verwenden, dass sie nicht so verwendet werden sollte, als ob sie rein funktional wäre. Es ist nicht so, dass OO-Sprachen das Böse waren. Es gibt Vorteile beider Ansätze, daher können wir jetzt den OO-Stil mit dem funktionalen Stil mischen und einige Codeteile funktionalisieren, während andere objektorientiert bleiben, so dass wir alle Vorteile der Wiederverwendbarkeit, Vererbung oder des Polimophismus nutzen können. Zum Glück sind wir nicht mehr an beide Ansätze gebunden. Warum versuchst du dich auf einen von ihnen zu beschränken?
Beantwortung Ihrer Frage: Nein, ich verwebe keine bestimmten Zustände durch die Anwendungslogik, sondern verwende das, was für den aktuellen Anwendungsfall angemessen ist, und wende die verfügbaren Techniken auf die am besten geeignete Weise an.
C # ist (noch) nicht so funktionsfähig, wie Sie es möchten.
quelle