Ich habe versucht, meinen Kopf um das Konzept der Monaden zu wickeln, und ich habe mit dem folgenden Beispiel experimentiert:Staat und IO Monaden
Ich habe eine Editor
Datentyp, der den Status eines Textes darstellt Dokument und einige Funktionen, die daran arbeiten.
data Editor = Editor {
lines :: [Line], -- editor contents are kept line by line
lineCount :: Int, -- holds length lines at all times
caret :: Caret -- the current caret position
-- ... some more definitions
} deriving (Show)
-- get the line at the given position (first line is at 0)
lineAt :: Editor -> Int -> Line
lineAt ed n = ls !! n
where
ls = lines ed
-- get the line that the caret is currently on
currentLine :: Editor -> Line
currentLine ed = lineAt ed $ currentY ed
-- move the caret horizontally by the specified amount of characters (can not
-- go beyond the current line)
moveHorizontally :: Editor -> Int -> Editor
moveHorizontally ed n = ed { caret = newPos }
where
Caret x y = caret ed
l = currentLine ed
mx = fromIntegral (L.length l - 1)
newX = clamp 0 mx (x+n)
newPos = Caret newX y
-- ... and lots more functions to work with an Editor
Alle diese Funktionen wirken sich auf einem Editor
, und viele von ihnen zurückkehren ein neues Editor
(wo der Cursor oder einen Text wurde geändert, verschoben wurde), so dachte ich, das ist eine gute Anwendung des State
sein könnte Monade und ich habe neu geschrieben meisten Editor
-Funktionen nun wie folgt aussehen:
lineAt' :: Int -> State Editor Line
lineAt' n = state $ \ed -> (lines ed !! n, ed)
currentLine' :: State Editor Line
currentLine' = do
y <- currentY'
lineAt' y
moveHorizontally' :: Int -> State Editor()
moveHorizontally' n = do
(Caret x y) <- gets caret
l <- currentLine'
let mx = fromIntegral (L.length l - 1)
let newX = clamp 0 mx (x+n)
modify (\ed -> ed { caret = Caret newX y })
moveHorizontally' :: Int -> State Editor()
moveHorizontally' n = do
(Caret x y) <- gets caret
l <- currentLine'
let mx = fromIntegral (L.length l - 1)
let newX = clamp 0 mx (x+n)
modify (\ed -> ed { caret = Caret newX y })
Das ist ziemlich genial, weil es mir Bearbeitungsaktionen innerhalb do
-Notation sehr leicht komponieren können.
Wie auch immer, jetzt habe ich Mühe, dies in einer tatsächlichen Anwendung zu verwenden. Angenommen, ich möchte dieses Editor
innerhalb einer Anwendung verwenden, die einige 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.
I würde eine andere State
monadisch haben müssen, den gesamten Anwendungszustand darstellt, der eine Editor
Instanz und einen Art-of Event-Loop, die den IO
monadisch von der Tastatur zum Lesen verwendet hält und rufen moveHorizontally'
die aktuellen AppState zu modifizieren durch Modifizieren sein Editor
.
Ich habe ein wenig zu diesem Thema gelesen und es scheint, als würde ich Monad Transformers verwenden, um einen Stapel von Monaden mit IO an der Unterseite zu bauen. Ich habe noch nie Monad Transformers benutzt und weiß nicht, was ich von hier machen soll? Ich habe auch herausgefunden, dass die State
Monade bereits einige Funktionen implementiert (es scheint ein Spezialfall eines Monade Transformers zu sein?), Aber ich bin verwirrt, wie man das verwendet?
„All diese Funktionen wirken sich auf einem Editor, und viele von ihnen einen neuen Editor zurückzukehren (wo der Caret verschoben wurde oder ein Text geändert wurde "- Das ist eine gute Sache! Es ist sehr unwahrscheinlich, dass eine einfache IO-Schleife, die Ihre ursprünglichen reinen Funktionen anwendet, einen neuen Editor im Speicher erzeugen würde.GHC kopiert die Struktur nur dann, wenn es erforderlich ist, und aktualisiert sie direkt, wenn die alte Referenz nicht verwendet wird. Sie müssen hier keine Transformatoren verwenden, und ohne sie wird Ihr Code klarer. – thumphries
@ DeX3 FYI, Einreichen eines * in sich geschlossenen * Post macht es viel einfacher für Menschen, Code zu schreiben, um Ihre Frage zu beantworten. – gallais