Cómo unir dos mónadas Haskell IO

Oct 02 2020

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

7 amalloy Oct 02 2020 at 06:25

(>>=)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)"?

6 RobinZigmond Oct 02 2020 at 04:00

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í.