Status und IO Monaden

8

Ich habe versucht, meinen Kopf um das Konzept der Monaden zu wickeln und experimentiere mit dem folgenden Beispiel:

Ich habe einen Editor -Datentyp, der den Zustand eines Textdokuments und einige Funktionen darstellt, die daran arbeiten.

%Vor%

Alle diese Funktionen wirken auf ein Editor , und viele von ihnen geben ein neues Editor zurück (wo das Caret verschoben wurde oder ein Text geändert wurde), also dachte ich, das wäre eine gute Anwendung des State monad und ich habe die meisten Editor -Funktionen so umgeschrieben, dass sie jetzt so aussehen:

%Vor%

Das ist ziemlich toll, weil ich damit sehr einfach Editieraktionen in do -notation erstellen kann.

Aber jetzt kämpfe ich darum, das in einer tatsächlichen Anwendung zu verwenden. Angenommen, ich möchte Editor in einer Anwendung verwenden, die IO ausführt. Angenommen, ich möchte eine Instanz von Editor jedes Mal manipulieren, wenn der Benutzer die Taste l auf der Tastatur drückt.

Ich müsste eine weitere State -Monade haben, die den gesamten Anwendungsstatus darstellt, der eine Editor -Instanz und eine Art von Ereignisschleife enthält, die die IO -Monade verwendet, um über die Tastatur zu lesen und moveHorizontally' aufruft. Ändern des aktuellen AppState durch Ändern von Editor .

Ich habe ein wenig über dieses Thema gelesen und es scheint, als müsste ich Monade-Transformer verwenden, um einen Stapel Monaden mit IO am unteren Rand zu erstellen. Ich habe noch nie Monad Transformers benutzt und weiß nicht, was ich von hier machen soll? Ich habe auch herausgefunden, dass die State monad bereits einige Funktionen implementiert (es scheint ein Spezialfall eines Monade Transformers zu sein?), Aber ich bin verwirrt, wie man das benutzt?

    
DeX3 11.08.2016, 07:41
quelle

2 Antworten

5

Lassen Sie uns zuerst ein wenig zurückgehen. Es ist immer am besten, Probleme isoliert zu haben. Lassen Sie reine Funktionen mit reinen Funktionen gruppieren, State - mit State und IO - mit IO. Das Ineinandergreifen mehrerer Konzepte ist ein bestimmtes Rezept zum Kochen von Code-Spaghetti. Du willst diese Mahlzeit nicht.

Nachdem wir das gesagt haben, stellen wir die reinen Funktionen wieder her und gruppieren sie in einem Modul. Wir werden jedoch kleine Änderungen vornehmen, um sie den Haskell-Konventionen anzupassen - nämlich, wir werden die Reihenfolge der Parameter ändern:

%Vor%

Wenn Sie nun wirklich Ihre State API zurückbekommen wollen, ist es einfach, sie in einem anderen Modul zu implementieren:

%Vor%

Wie Sie jetzt sehen, können Sie mit den Standardkonventionen die standardmäßigen State Dienstprogramme wie gets und modify , um die bereits implementierten Funktionen auf die State monad zu reduzieren.

Tatsächlich funktionieren die genannten Utilities aber auch für den StateT monad-transformer, von dem State eigentlich nur ein Sonderfall ist. So können wir das Gleiche auch allgemeiner umsetzen:

%Vor%

Wie Sie sehen, werden nur die Typ-Signaturen geändert.

Jetzt können Sie diese allgemeinen Funktionen in Ihrem Transformatorstapel verwenden. Z. B.

%Vor%

Damit haben wir gerade eine granulare Isolierung von Bedenken und eine große Flexibilität unserer Codebasis erreicht.

Das ist natürlich ein ziemlich primitives Beispiel. Normalerweise hat der letzte Monade-Transformator-Stapel mehr Pegel. Z. B.

%Vor%

Um zwischen all diesen Ebenen zu springen, ist der typische Werkzeugsatz die Funktion lift oder die Bibliothek" mtl ", die Typklassen bereitstellt um die Verwendung von lift zu reduzieren. Ich muss jedoch erwähnen, dass nicht jeder (auch ich selbst) ein Fan von "mtl" ist, da es zwar die Menge an Code reduziert, aber eine gewisse Mehrdeutigkeit und logische Komplexität mit sich bringt. Ich bevorzuge lift explizit.

Der Punkt von Transformatoren ist, dass Sie eine existierende Monade (Transformator-Stack ist auch eine Monade) mit einigen neuen Funktionen ad hoc ergänzen können.

Wie bei Ihrer Frage zur Erweiterung des Status der App können Sie einfach eine weitere StateT-Ebene zu Ihrem Stapel hinzufügen:

%Vor%     
Nikita Volkov 11.08.2016, 10:14
quelle
0

Wenn Sie mtl verwenden, müssen Sie sich nicht für einen Monad-Stack festlegen, insbesondere nicht bis zu dem Punkt in Ihrem Programm, an dem Sie sich befinden führen Sie die Effekte tatsächlich aus. Dies bedeutet, dass Sie den Stapel problemlos ändern können, um zusätzliche Ebenen hinzuzufügen, eine andere Fehlerberichts-Strategie auszuwählen usw., usw.

Alles, was Sie tun müssen, ist, die -XFlexibleContexts Spracherweiterung zu aktivieren, indem Sie die folgende Zeile am Anfang Ihrer Datei hinzufügen:

%Vor%

Importieren Sie das Modul, das die Klasse MonadState definiert:

%Vor%

Ändern Sie die Typannotation Ihrer Programme, um der Tatsache Rechnung zu tragen, dass Sie diesen Ansatz jetzt verwenden. Die Einschränkungen MonadState Editor m => besagt, dass m eine Monade ist, die irgendwo einen Zustand vom Typ Editor hat.

%Vor%

Nehmen wir an, Sie möchten jetzt eine Zeile von stdin lesen und in die Liste der Zeilen schieben (in der Praxis würden Sie wahrscheinlich die Zeichen nach dem aktuellen carret einfügen und sie entsprechend verschieben, aber die allgemeine Idee ist das Gleiche). Sie können einfach die Einschränkung MonadIO verwenden, um anzuzeigen, dass Sie für diese Funktion% IO benötigen:

%Vor%     
gallais 11.08.2016 10:08
quelle