Benjamin Pierce sagte in TAPL
Ein Typsystem kann als Berechnung einer Art statischer Annäherung an das Laufzeitverhalten der Begriffe in einem Programm angesehen werden.
Deshalb ist eine Sprache, die mit einem leistungsfähigen Schriftsystem ausgestattet ist, streng ausdrucksstärker als eine schlecht typisierte Sprache. Sie können genauso über Monaden denken.
Als @Carl- und Sigfpe- Punkt können Sie einen Datentyp mit allen gewünschten Operationen ausstatten, ohne auf Monaden, Typklassen oder andere abstrakte Dinge zurückgreifen zu müssen. Mit Monaden können Sie jedoch nicht nur wiederverwendbaren Code schreiben, sondern auch alle redundanten Details abstrahieren.
Angenommen, wir möchten eine Liste filtern. Der einfachste Weg ist die Verwendung der filter
Funktion : filter (> 3) [1..10]
, die gleich ist [4,5,6,7,8,9,10]
.
Eine etwas kompliziertere Version von filter
, die auch einen Akkumulator von links nach rechts durchläuft, ist
swap (x, y) = (y, x)
(.*) = (.) . (.)
filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]
Um alles i
so zu bekommen , dass i <= 10, sum [1..i] > 4, sum [1..i] < 25
wir schreiben können
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
was gleich ist [3,4,5,6]
.
Oder wir können die nub
Funktion, mit der doppelte Elemente aus einer Liste entfernt werden, neu definieren filterAccum
:
nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []
nub' [1,2,4,5,4,3,1,8,9,4]
gleich [1,2,4,5,3,8,9]
. Hier wird eine Liste als Akkumulator übergeben. Der Code funktioniert, weil es möglich ist, die Listenmonade zu verlassen, sodass die gesamte Berechnung rein bleibt (wird notElem
eigentlich nicht verwendet >>=
, könnte es aber). Es ist jedoch nicht möglich, die E / A-Monade sicher zu verlassen (dh Sie können keine E / A-Aktion ausführen und einen reinen Wert zurückgeben - der Wert wird immer in die E / A-Monade eingeschlossen). Ein weiteres Beispiel sind veränderbare Arrays: Nachdem Sie die ST-Monade verlassen haben, in der sich ein veränderliches Array befindet, können Sie das Array nicht mehr in konstanter Zeit aktualisieren. Wir brauchen also eine monadische Filterung aus dem Control.Monad
Modul:
filterM :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ [] = return []
filterM p (x:xs) = do
flg <- p x
ys <- filterM p xs
return (if flg then x:ys else ys)
filterM
führt eine monadische Aktion für alle Elemente aus einer Liste aus und ergibt Elemente, für die die monadische Aktion zurückgegeben wird True
.
Ein Filterbeispiel mit einem Array:
nub' xs = runST $ do
arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
let p i = readArray arr i <* writeArray arr i False
filterM p xs
main = print $ nub' [1,2,4,5,4,3,1,8,9,4]
druckt [1,2,4,5,3,8,9]
wie erwartet.
Und eine Version mit der IO-Monade, in der gefragt wird, welche Elemente zurückgegeben werden sollen:
main = filterM p [1,2,4,5] >>= print where
p i = putStrLn ("return " ++ show i ++ "?") *> readLn
Z.B
return 1? -- output
True -- input
return 2?
False
return 4?
False
return 5?
True
[1,5] -- output
Und als letzte Illustration filterAccum
kann definiert werden in Bezug auf filterM
:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
Die StateT
Monade, die unter der Haube verwendet wird, ist nur ein gewöhnlicher Datentyp.
Dieses Beispiel zeigt, dass Sie mit Monaden nicht nur den Rechenkontext abstrahieren und sauberen wiederverwendbaren Code schreiben können (aufgrund der Zusammensetzbarkeit von Monaden, wie @Carl erklärt), sondern auch benutzerdefinierte Datentypen und integrierte Grundelemente einheitlich behandeln können.