Benjamin Pierce a déclaré dans TAPL
Un système de types peut être considéré comme calculant une sorte d'approximation statique des comportements d'exécution des termes dans un programme.
C'est pourquoi un langage équipé d'un système de typage puissant est strictement plus expressif, qu'un langage mal typé. Vous pouvez penser aux monades de la même manière.
En tant que point @Carl et sigfpe , vous pouvez équiper un type de données avec toutes les opérations que vous souhaitez sans recourir à des monades, des classes de caractères ou tout autre élément abstrait. Cependant, les monades vous permettent non seulement d'écrire du code réutilisable, mais aussi d'abstraire tous les détails redondants.
Par exemple, disons que nous voulons filtrer une liste. La manière la plus simple est d'utiliser la filter
fonction filter (> 3) [1..10]
:, qui est égale à [4,5,6,7,8,9,10]
.
Une version légèrement plus compliquée de filter
, qui passe également un accumulateur de gauche à droite, est
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]
Pour tout i
avoir i <= 10, sum [1..i] > 4, sum [1..i] < 25
, on peut écrire
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
ce qui équivaut [3,4,5,6]
.
Ou nous pouvons redéfinir la nub
fonction, qui supprime les éléments en double d'une liste, en termes de filterAccum
:
nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []
nub' [1,2,4,5,4,3,1,8,9,4]
est égal [1,2,4,5,3,8,9]
. Une liste est passée ici comme un accumulateur. Le code fonctionne, car il est possible de quitter la liste monade, donc le calcul entier reste pur ( notElem
n'utilise pas >>=
réellement, mais il pourrait). Cependant, il n'est pas possible de quitter la monade IO en toute sécurité (c'est-à-dire que vous ne pouvez pas exécuter une action IO et renvoyer une valeur pure - la valeur sera toujours enveloppée dans la monade IO). Un autre exemple est les tableaux mutables: après avoir quitté la monade ST, où un tableau mutable est actif, vous ne pouvez plus mettre à jour le tableau en temps constant. Nous avons donc besoin d'un filtrage monadique du Control.Monad
module:
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
exécute une action monadique pour tous les éléments d'une liste, produisant des éléments, pour lesquels l'action monadique revient True
.
Un exemple de filtrage avec un tableau:
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]
imprime [1,2,4,5,3,8,9]
comme prévu.
Et une version avec la monade IO, qui demande quels éléments retourner:
main = filterM p [1,2,4,5] >>= print where
p i = putStrLn ("return " ++ show i ++ "?") *> readLn
Par exemple
return 1? -- output
True -- input
return 2?
False
return 4?
False
return 5?
True
[1,5] -- output
Et comme illustration finale, filterAccum
peut être défini en termes de filterM
:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
avec la StateT
monade, qui est utilisée sous le capot, étant juste un type de données ordinaire.
Cet exemple illustre que les monades vous permettent non seulement d'abstraire le contexte de calcul et d'écrire du code réutilisable propre (en raison de la composabilité des monades, comme l'explique @Carl), mais également de traiter uniformément les types de données définis par l'utilisateur et les primitives intégrées.