Haskell Language
Mónadas
Buscar..
Introducción
Una mónada es un tipo de datos de acciones compuestas. Monad
es la clase de constructores de tipo cuyos valores representan tales acciones. Tal vez IO
es el más reconocible uno: un valor de IO a
es una "receta para recuperar un a
valor en el mundo real".
Decimos que un constructor de tipo m
(como []
o Maybe
) forma una mónada si hay una instance Monad m
cumple ciertas leyes sobre la composición de las acciones. Entonces podemos razonar sobre ma
como una "acción cuyo resultado tiene el tipo a
".
La mónada Tal vez
Maybe
se utiliza para representar valores posiblemente vacíos, similar a null
en otros idiomas. Normalmente se usa como el tipo de salida de funciones que pueden fallar de alguna manera.
Considera la siguiente función:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Piense en la halve
como una acción, dependiendo de un Int
, que intenta reducir a la mitad el número entero, fallando si es impar.
¿Cómo halve
un número entero tres veces?
takeOneEighth :: Int -> Maybe Int -- (after you read the 'do' sub-section:)
takeOneEighth x =
case halve x of -- do {
Nothing -> Nothing
Just oneHalf -> -- oneHalf <- halve x
case halve oneHalf of
Nothing -> Nothing
Just oneQuarter -> -- oneQuarter <- halve oneHalf
case halve oneQuarter of
Nothing -> Nothing -- oneEighth <- halve oneQuarter
Just oneEighth ->
Just oneEighth -- return oneEighth }
-
takeOneEighth
es una secuencia de tres pasos de lahalve
encadenados. - Si un paso de la
halve
falla, queremos que la composición completatakeOneEighth
falle. - Si un paso de la
halve
tiene éxito, queremos canalizar su resultado hacia adelante.
instance Monad Maybe where
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= f = Nothing -- infixl 1 >>=
Just x >>= f = Just (f x) -- also, f =<< m = m >>= f
-- return :: a -> Maybe a
return x = Just x
y ahora podemos escribir:
takeOneEighth :: Int -> Maybe Int
takeOneEighth x = halve x >>= halve >>= halve -- or,
-- return x >>= halve >>= halve >>= halve -- which is parsed as
-- (((return x) >>= halve) >>= halve) >>= halve -- which can also be written as
-- (halve =<<) . (halve =<<) . (halve =<<) $ return x -- or, equivalently, as
-- halve <=< halve <=< halve $ x
La composición de Kleisli <=<
se define como (g <=< f) x = g =<< fx
, o equivalentemente como (f >=> g) x = fx >>= g
. Con ello la definición anterior se convierte en justa.
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Hay tres leyes de la mónada que deben ser obedecidas por cada mónada, es decir, cada tipo que es una instancia de la clase de tipos de la Monad
:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
donde m
es una mónada, f
tiene tipo a -> mb
y g
tiene tipo b -> mc
.
O de manera equivalente, utilizando el operador de composición Kleisli >=>
definido anteriormente:
1. return >=> g = g -- do { y <- return x ; g y } == g x
2. f >=> return = f -- do { y <- f x ; return y } == f x
3. (f >=> g) >=> h = f >=> (g >=> h) -- do { z <- do { y <- f x; g y } ; h z }
-- == do { y <- f x ; do { z <- g y; h z } }
Obedecer estas leyes hace que sea mucho más fácil razonar acerca de la mónada, porque garantiza que el uso de funciones monádicas y su composición se comporte de una manera razonable, similar a otras mónadas.
Vamos a comprobar si la mónada Maybe
obedece las tres leyes de la mónada.
- La ley de identidad de la izquierda -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- La ley de identidad correcta -
m >>= return = m
-
Just
constructor de datos
Just z >>= return
= return z
= Just z
- Constructor de
Nothing
datos
Nothing >>= return
= Nothing
- La ley de asociatividad -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
constructor de datos
-- Left-hand side
((Just z) >>= f) >>= g
= f z >>= g
-- Right-hand side
(Just z) >>= (\x -> f x >>= g)
(\x -> f x >>= g) z
= f z >>= g
- Constructor de
Nothing
datos
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO mónada
No hay forma de obtener un valor de tipo a
fuera de una expresión de tipo IO a
y no debería haber. Esto es en realidad una gran parte de por qué las mónadas se utilizan para modelar IO
.
Se puede considerar que una expresión de tipo IO a
representa una acción que puede interactuar con el mundo real y, si se ejecuta, daría lugar a algo de tipo a
. Por ejemplo, la función getLine :: IO String
del preludio no significa que debajo de getLine
haya una cadena específica que pueda extraer; significa que getLine
representa la acción de obtener una línea desde la entrada estándar.
No es sorprendente que main :: IO ()
ya que un programa Haskell representa una computación / acción que interactúa con el mundo real.
Lo que puede hacer con expresiones de tipo IO a
porque IO
es una mónada:
Secuencia dos acciones usando
(>>)
para producir una nueva acción que ejecuta la primera acción, descarta cualquier valor que produzca y luego ejecuta la segunda acción.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
A veces no desea descartar el valor que se produjo en la primera acción; en realidad, le gustaría que se introdujera en una segunda acción. Para eso, tenemos
>>=
. ParaIO
, tiene tipo(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Tome un valor normal y conviértalo en una acción que inmediatamente devuelva el valor que le dio. Esta función es menos útil, obviamente, hasta que empiece a usar notación
do
-- make an action that just returns 5 return 5
Más de la Wiki de Haskell en la mónada IO aquí .
Lista Mónada
Las listas forman una mónada. Tienen una instanciación de mónada equivalente a esta:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Podemos usarlos para emular el no determinismo en nuestros cálculos. Cuando usamos xs >>= f
, la función f :: a -> [b]
se mapea sobre la lista xs
, obteniendo una lista de listas de resultados de cada aplicación de f
sobre cada elemento de xs
, y todas las listas de los resultados se concatenan en una lista de todos los resultados. Como ejemplo, calculamos una suma de dos números no deterministas utilizando do-notation , representada por la lista de sumas de todos los pares de enteros de dos listas, cada una de las cuales representa todos los valores posibles de un número no determinista:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
O equivalentemente, usando liftM2
en Control.Monad
:
sumnd = liftM2 (+)
obtenemos:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
La mónada como subclase de aplicativo
A partir de GHC 7.10, el Applicative
es una superclase de la Monad
(es decir, todo tipo que sea una Monad
también debe ser un Applicative
). Todos los métodos de Applicative
( pure
, <*>
) se pueden implementar en términos de métodos de Monad
( return
, >>=
).
Es obvio que los propósitos pure
y de return
son equivalentes, tan pure = return
. La definición de <*>
es demasiado clara:
mf <*> mx = do { f <- mf; x <- mx; return (f x) }
-- = mf >>= (\f -> mx >>= (\x -> return (f x)))
-- = [r | f <- mf, x <- mx, r <- return (f x)] -- with MonadComprehensions
-- = [f x | f <- mf, x <- mx]
Esta función se define como ap
en las bibliotecas estándar.
Por lo tanto, si ya ha definido una instancia de Monad
para un tipo, efectivamente puede obtener una instancia de Applicative
para ella "gratis" definiendo
instance Applicative < type > where
pure = return
(<*>) = ap
Al igual que con las leyes de la mónada, estas equivalencias no se aplican, pero los desarrolladores deben asegurarse de que siempre se respeten.
No hay forma general de extraer valor de un cálculo monádico
Puede ajustar valores en acciones y canalizar el resultado de un cálculo en otro:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Sin embargo, la definición de una Mónada no garantiza la existencia de una función de tipo Monad m => ma -> a
.
Eso significa que, en general, no hay forma de extraer un valor de un cálculo (es decir, "desenvolverlo"). Este es el caso de muchos casos:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
Específicamente, no hay ninguna función IO a -> a
, que a menudo confunde a los principiantes; ver este ejemplo
hacer notación
do
anotación es azúcar sintáctica para las mónadas. Estas son las reglas:
do x <- mx do x <- mx y <- my is equivalent to do y <- my ... ...
do let a = b let a = b in ... is equivalent to do ...
do m m >> ( e is equivalent to e)
do x <- m m >>= (\x -> e is equivalent to e)
do m is equivalent to m
Por ejemplo, estas definiciones son equivalentes:
example :: IO Integer
example =
putStrLn "What's your name?" >> (
getLine >>= (\name ->
putStrLn ("Hello, " ++ name ++ ".") >> (
putStrLn "What should we return?" >> (
getLine >>= (\line ->
let n = (read line :: Integer) in
return (n + n))))))
example :: IO Integer
example = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ ".")
putStrLn "What should we return?"
line <- getLine
let n = (read line :: Integer)
return (n + n)
Definición de mónada
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
La función más importante para tratar con las mónadas es el operador de enlace >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Piense en
ma
como "una acción con una
resultado". - Piense en
a -> mb
“una acción (en función de una comoa
parámetro) con unab
. Consecuencia”.
>>=
secuencia dos acciones juntas canalizando el resultado de la primera acción a la segunda.
La otra función definida por Monad
es:
return :: a -> m a
Su nombre es desafortunado: este return
no tiene nada que ver con la palabra clave de return
encontrada en lenguajes de programación imperativos.
return x
es la acción trivial que produce x
como su resultado. (Es trivial en el siguiente sentido :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law