Es tut mir leid für eine weitere Frage zu FP + -Nebenwirkungen, aber ich konnte keine existierende finden, die dies für mich ganz beantwortete.
Mein (begrenztes) Verständnis der funktionalen Programmierung ist, dass Zustände / Nebenwirkungen minimiert und von zustandsloser Logik getrennt werden sollten.
Ich nehme auch Haskells Ansatz dazu zur Kenntnis, die IO-Monade, die dies erreicht, indem sie zustandsbezogene Aktionen zur späteren Ausführung in einen Container packt, der über den Rahmen des Programms hinausgeht.
Ich versuche, dieses Muster zu verstehen, aber tatsächlich zu bestimmen, ob es in einem Python-Projekt verwendet werden soll, also möchte ich Haskell-Besonderheiten vermeiden, wenn möglich.
Rohes Beispiel eingehend.
Wenn mein Programm eine XML-Datei in eine JSON-Datei konvertiert:
def main():
xml_data = read_file('input.xml') # impure
json_data = convert(xml_data) # pure
write_file('output.json', json_data) # impure
Ist der Ansatz der IO-Monade nicht effektiv, um dies zu tun:
steps = list(
read_file,
convert,
write_file,
)
dann entlasten Sie sich von der Verantwortung, indem Sie diese Schritte nicht tatsächlich aufrufen , sondern den Dolmetscher dies tun lassen?
Oder anders ausgedrückt, es ist wie beim Schreiben:
def main(): # pure
def inner(): # impure
xml_data = read_file('input.xml')
json_data = convert(xml_data)
write_file('output.json', json_data)
return inner
Wenn Sie dann erwarten, dass jemand anderes anruft inner()
und sagt, dass Ihre Arbeit erledigt ist, weil sie main()
rein ist.
Das gesamte Programm wird im Grunde genommen in der IO-Monade enthalten sein.
Wenn der Code tatsächlich ausgeführt wird , hängt alles nach dem Lesen der Datei vom Status der Datei ab, sodass immer noch dieselben statusbezogenen Fehler wie bei der Imperativimplementierung auftreten. Haben Sie also als Programmierer, der dies verwaltet, tatsächlich etwas gewonnen?
Ich weiß den Vorteil des Reduzierens und Isolierens von staatlichem Verhalten sehr zu schätzen , weshalb ich die imperative Version so strukturiert habe: Inputs sammeln, reine Sachen machen, Outputs ausspucken. Hoffentlich convert()
kann man völlig rein sein und die Vorteile von Cachefähigkeit, Threadsicherheit usw. nutzen.
Ich weiß auch zu schätzen, dass monadische Typen nützlich sein können, insbesondere in Pipelines, die mit vergleichbaren Typen betrieben werden, aber ich verstehe nicht, warum IO Monaden verwenden sollte, es sei denn, sie befinden sich bereits in einer solchen Pipeline.
Gibt es einen zusätzlichen Vorteil beim Umgang mit Nebenwirkungen, die das E / A-Monadenmuster mit sich bringt, das mir fehlt?
main
in einem Haskell-ProgrammIO ()
- eine E / A-Aktion ist. Dies ist eigentlich gar keine Funktion; Es ist ein Wert . Ihr gesamtes Programm ist ein reiner Wert, der Anweisungen enthält, die der Laufzeitsprache mitteilen, was sie tun soll. Alle unreinen Dinge (die tatsächlich die E / A-Aktionen ausführen) liegen außerhalb des Bereichs Ihres Programms.read_file
) als Argument für die nächste verwenden (write_file
). Wenn Sie nur eine Sequenz unabhängiger Aktionen hätten, bräuchten Sie keine Monade.Antworten:
Das ist der Punkt, an dem ich glaube, dass Sie es aus Sicht der Haskeller nicht sehen. Wir haben also ein Programm wie dieses:
Ich denke, eine typische Einstellung von Haskeller wäre, dass
convert
der reine Teil:IO
Teile;IO
überhaupt darum kümmern zu müssen .Sie sehen dies also nicht als
convert
"enthalten" anIO
, sondern als isoliert vonIO
. Was immer diesconvert
tut, kann niemals von etwas abhängen, das in einerIO
Handlung geschieht .Ich würde sagen, dass dies in zwei Dinge unterteilt ist:
convert
vom Status der Datei ab.convert
Funktion macht , hängt nicht vom Zustand der Datei ab.convert
ist immer die gleiche Funktion , auch wenn sie an verschiedenen Stellen mit unterschiedlichen Argumenten aufgerufen wird.Dies ist ein etwas abstrakter Punkt, aber es ist wirklich der Schlüssel zu dem, was Haskellers meinen, wenn sie darüber sprechen. Sie möchten so schreiben
convert
, dass bei jedem gültigen Argument ein korrektes Ergebnis für dieses Argument erzeugt wird. Wenn Sie es so betrachten, geht die Tatsache, dass das Lesen einer Datei eine zustandsbehaftete Operation ist, nicht in die Gleichung ein. Alles, was zählt, ist, dass jedes Argument, das ihm zugeführt wird und wo immer es herkommt,convert
es richtig handhaben muss. Und die Tatsache, dass Reinheit die Möglichkeitenconvert
der Eingabe einschränkt, vereinfacht diese Überlegungen.Wenn also
convert
aus einigen Argumenten falsche Ergebnisse hervorgehen undreadFile
sie zu einem solchen Argument machen, sehen wir das nicht als Fehler, der durch state verursacht wurde . Es ist ein Fehler in einer reinen Funktion!quelle
Es ist schwer, genau zu wissen, was Sie mit "rein akademisch" meinen, aber ich denke, die Antwort lautet meistens "nein".
Wie in Simon Peyton Jones ' Tackling the Awkward Squad ( dringend empfohlene Lektüre!) Erläutert , sollte monadische E / A echte Probleme mit der Art und Weise lösen, wie Haskell mit E / A umgeht. Lesen Sie das Beispiel des Servers mit Anfragen und Antworten, das ich hier nicht kopieren werde. es ist sehr lehrreich.
Haskell unterstützt im Gegensatz zu Python einen Stil der "reinen" Berechnung, der durch sein Typsystem erzwungen werden kann. Natürlich können Sie sich selbst disziplinieren, wenn Sie in Python programmieren, um diesem Stil zu entsprechen, aber was ist mit Modulen, die Sie nicht geschrieben haben? Ohne viel Hilfe des Typsystems (und der allgemeinen Bibliotheken) ist monadische E / A in Python wahrscheinlich weniger nützlich. Die Philosophie der Sprache ist einfach nicht dazu gedacht, eine strikte Trennung von rein und unrein durchzusetzen.
Beachten Sie, dass dies mehr über die unterschiedlichen Philosophien von Haskell und Python aussagt als darüber, wie akademisch monadisches I / O ist. Ich würde es nicht für Python verwenden.
Eine andere Sache. Du sagst:
Es ist wahr, dass die Haskell-
main
Funktion "lebt"IO
, aber echte Haskell-Programme sollten nicht verwendet werden,IO
wenn sie nicht benötigt werden. Fast jede Funktion, die Sie schreiben und für die keine E / A erforderlich ist, sollte keinen Typ habenIO
.Also würde ich in Ihrem letzten Beispiel sagen, dass Sie es falsch verstanden haben: Es
main
ist unrein (weil es Dateien liest und schreibt), aber Kernfunktionen wieconvert
sind rein.quelle
Warum ist IO unrein? Weil es zu unterschiedlichen Zeiten unterschiedliche Werte zurückgeben kann. Es gibt eine Zeitabhängigkeit, die auf die eine oder andere Weise berücksichtigt werden muss . Dies ist umso wichtiger, wenn die Auswertung faul ist. Betrachten Sie das folgende Programm:
Warum würde die erste Eingabeaufforderung ohne eine E / A-Monade jemals ausgegeben werden? Es gibt nichts, was davon abhängt, so dass eine faule Bewertung bedeutet, dass es niemals gefordert wird. Es gibt auch nichts, was die Eingabeaufforderung dazu zwingt, ausgegeben zu werden, bevor die Eingabe gelesen wird. Für den Computer sind diese ersten beiden Ausdrücke ohne E / A-Monade völlig unabhängig voneinander. Glücklicherweise
name
erlegt er den zweiten beiden einen Befehl auf.Es gibt andere Möglichkeiten, um das Problem der Auftragsabhängigkeit zu lösen, aber die Verwendung einer E / A-Monade ist wahrscheinlich die einfachste Möglichkeit (zumindest aus sprachlicher Sicht), um zu ermöglichen, dass alles im faulen Funktionsbereich bleibt, ohne kleine Teile des Imperativcodes. Es ist auch das flexibelste. Beispielsweise können Sie relativ einfach eine E / A-Pipeline dynamisch zur Laufzeit basierend auf Benutzereingaben erstellen.
quelle
Das ist nicht nur funktionale Programmierung; Das ist normalerweise eine gute Idee in jeder Sprache. Wenn Sie Unit-Tests durchführen, ist die Art und Weise, wie Sie sich aufteilen
read_file()
, völlig normal, da das Schreiben von Tests relativ einfach ist , obwohl es bei weitem der komplexeste und umfangreichste Teil des Codes ist: Sie müssen lediglich den Eingabeparameter einrichten . Das Schreiben von Tests für und ist etwas schwieriger (obwohl die Funktionen selbst fast trivial sind), da Sie vor und nach dem Aufrufen der Funktion Dinge im Dateisystem erstellen und / oder lesen müssen. Im Idealfall machen Sie solche Funktionen so einfach, dass Sie sich wohl fühlen, wenn Sie sie nicht testen, und sparen sich so viel Aufwand.convert()
write_file()
convert()
read_file()
write_file()
Der Unterschied zwischen Python und Haskell besteht darin, dass Haskell über eine Typprüfung verfügt, mit der nachgewiesen werden kann, dass Funktionen keine Nebenwirkungen haben. In Python müssen Sie , dass niemand hoffen , zufällig in einer Datei-Lesen oder -writing Funktion in fallen gelassen
convert()
(etwaread_config_file()
). Wenn Sie in Haskellconvert :: String -> String
ohneIO
Monade deklarieren oder ähnliches, garantiert die Typprüfung, dass dies eine reine Funktion ist, die nur auf ihren Eingabeparametern und nichts anderem beruht. Wenn jemand versucht, Änderungen vorzunehmenconvert
, um eine Konfigurationsdatei zu lesen, werden schnell Compilerfehler angezeigt, die darauf hinweisen, dass sie die Reinheit der Funktion beeinträchtigen würden. (Und hoffentlich wären sie vernünftig genug, umread_config_file
dasconvert
Ergebnis zu verlassen und es weiterzugebenconvert
, um die Reinheit zu bewahren.)quelle