Cómo unir dos mónadas Haskell IO
El siguiente programa Haskell (en funcionamiento) genera un hechizo aleatorio:
import System.Random
spells =
[ "Abracadabra!"
, "Hocus pocus!"
, "Simsalabim!"
]
main :: IO()
main = do
spell <- (spells !!) <$> randomRIO (0, length spells - 1)
putStrLn spell
Sin embargo, la variable spelles bastante inútil. Almacena la cadena aleatoria seleccionada de la lista de hechizos, pero luego se pasa inmediatamente a la putStrLnfunción y nunca se vuelve a utilizar. Intenté combinar las dos operaciones IO en una sola línea como esta:
main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)
Pero tengo el siguiente error:
• Couldn't match type ‘IO ()’ with ‘()’
Expected type: Int -> ()
Actual type: Int -> IO ()
• In the first argument of ‘(<$>)’, namely
‘putStrLn <$> (spells !!)’ In the expression: putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1) In an equation for ‘main’: main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1) | 160 | main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)
| ^^^^^^^^^^^^^^^^^^^^^^^^
¿Hay alguna forma de combinar las dos operaciones IO en una sola línea? Miré esta pregunta similar pero no pude entender la respuesta.
Respuestas
(>>=)es el operador de mónada "canónico", como se indica en la respuesta de Robin Zigmond. Sin embargo, si usted está tratando de escribir código en un estilo similar al aplicativo, a menudo me gusta usar su versión volteada, (=<<). Tiene una buena simetría con las funciones en Functor y Applicative, y cómo se asemejan a una llamada de función no monádica ordinaria con solo un operador adicional interpuesto:
f x -- one-argument function call
f <$> fx -- fmapping that function into a functor g x y -- two-argument function call g <$> ax <*> ay -- applied over two applicatives
f =<< mx -- binding a function with a monadic value
mx >>= f -- looks backwards, doesn't it?
Entonces tu expresión podría escribirse
main = putStrLn =<< (spells !!) <$> randomRIO (0, length spells - 1)
Personalmente, prefiero usar una composición de funciones más ordinaria y un mapeo menos contextual, por lo que movería el (spells !!)a la izquierda del operador de enlace:
main = putStrLn . (spells !!) =<< randomRIO (0, length spells - 1)
¿Ves cómo se lee un poco bien de esta manera? ¿"Imprime el hechizo en el índice dado por randomRIO (0, length spells - 1)"?
dola notación es simplemente azúcar sintáctica para el uso del operador de enlace ( >>=). Entonces su dobloque de 2 líneas podría reescribirse:
main = do
spells <- (spells !!) <$> randomRIO (0, length spells - 1) putStrLn spells -- rewrite to bind notation main = ((spells !!) <$> randomRIO (0, length spells - 1)) >>= \spells ->
putStrLn spells
-- simplify the lambda
main = ((spells !!) <$> randomRIO (0, length spells - 1)) >>= putStrLn
Sin embargo, me preguntaría si hay alguna legibilidad u otra ganancia al hacer esto aquí.