Benjamin Pierce disse na TAPL
Um sistema de tipos pode ser considerado como calculando um tipo de aproximação estática aos comportamentos em tempo de execução dos termos em um programa.
É por isso que um idioma equipado com um poderoso sistema de tipos é estritamente mais expressivo do que um idioma mal digitado. Você pode pensar em mônadas da mesma maneira.
Como @Carl e ponto sigfpe , você pode equipar um tipo de dados com todas as operações que desejar, sem recorrer a mônadas, classes de tipo ou qualquer outra coisa abstrata. No entanto, as mônadas permitem não apenas escrever código reutilizável, mas também abstrair todos os detalhes redundantes.
Como exemplo, digamos que queremos filtrar uma lista. A maneira mais simples é usar a filter
função filter (> 3) [1..10]
:, que é igual a [4,5,6,7,8,9,10]
.
Uma versão um pouco mais complicada filter
, que também passa um acumulador da esquerda para a direita, é
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]
Para obter tudo i
isso i <= 10, sum [1..i] > 4, sum [1..i] < 25
, podemos escrever
filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]
o que é igual [3,4,5,6]
.
Ou podemos redefinir a nub
função, que remove elementos duplicados de uma lista, em termos de filterAccum
:
nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []
nub' [1,2,4,5,4,3,1,8,9,4]
é igual [1,2,4,5,3,8,9]
. Uma lista é passada como um acumulador aqui. O código funciona, porque é possível deixar a lista em mônada, para que todo o cálculo permaneça puro ( na verdade notElem
não é usado >>=
, mas poderia). No entanto, não é possível sair com segurança da mônada de E / S (ou seja, você não pode executar uma ação de E / S e retornar um valor puro - o valor sempre será envolvido na mônada de E / S). Outro exemplo são matrizes mutáveis: depois de sair da mônada ST, onde uma matriz mutável fica ativa, você não pode mais atualizar a matriz em tempo constante. Então, precisamos de uma filtragem monádica do Control.Monad
módulo:
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
executa uma ação monádica para todos os elementos de uma lista, produzindo elementos, para os quais a ação monádica retorna True
.
Um exemplo de filtragem com uma matriz:
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]
conforme o esperado.
E uma versão com a mônada IO, que pergunta quais elementos retornar:
main = filterM p [1,2,4,5] >>= print where
p i = putStrLn ("return " ++ show i ++ "?") *> readLn
Por exemplo
return 1? -- output
True -- input
return 2?
False
return 4?
False
return 5?
True
[1,5] -- output
E, como ilustração final, filterAccum
pode ser definido em termos de filterM
:
filterAccum f a xs = evalState (filterM (state . flip f) xs) a
com a StateT
mônada, usada sob o capô, sendo apenas um tipo de dados comum.
Este exemplo ilustra que as mônadas não apenas permitem abstrair o contexto computacional e escrever código reutilizável limpo (devido à composibilidade das mônadas, como explica @Carl), mas também tratar tipos de dados definidos pelo usuário e primitivas incorporadas de maneira uniforme.