How to join two Haskell IO monads

493 views Asked by At

The following (working) Haskell program outputs a random spell:

import System.Random

spells =
  [ "Abracadabra!"
  , "Hocus pocus!"
  , "Simsalabim!"
  ]

main :: IO()
main = do
  spell <- (spells !!) <$> randomRIO (0, length spells - 1)
  putStrLn spell

However, the variable spell is quite useless. It stores the random string selected from the list of spells, but is then immediately passed to the putStrLn function and is never used again. I tried to combine the two IO operations into a single line like this:

main = putStrLn <$> (spells !!) <$> randomRIO (0, length spells - 1)

But I got the following 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)
    |        ^^^^^^^^^^^^^^^^^^^^^^^^

Is there a way to combine the two IO operations into a single line? I looked at this similar question but I couldn't understand the answer.

2

There are 2 answers

0
amalloy On BEST ANSWER

(>>=) is the "canonical" monad operator, as given in Robin Zigmond's answer. However, if you're trying to write code in an applicative-like style, I often enjoy using its flipped version, (=<<). It has a nice symmetry with the functions in Functor and Applicative, and how they resemble an ordinary non-monadic function call with just an extra operator interposed:

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?

So your expression could be written

main = putStrLn =<< (spells !!) <$> randomRIO (0, length spells - 1)

Personally I'd rather use more ordinary function composition and less contextual mapping, so I'd move the (spells !!) to the left of the bind operator:

main = putStrLn . (spells !!) =<< randomRIO (0, length spells - 1)

See how it kinda reads nicely in order this way? "Print out the spell at the index given by randomRIO (0, length spells - 1)"?

0
Robin Zigmond On

do notation is just syntactic sugar for use of the bind operator (>>=). So your 2-line do block could be rewritten:

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 

However I would question whether there is any readability, or other, gain from doing this here.