7

I am still struggling with Haskell and now I have encountered a problem with wrapping my mind around the Input/Output monad from this example:

main = do   
line <- getLine  
if null line  
    then return ()  
    else do  
        putStrLn $ reverseWords line  
        main  
  
reverseWords :: String -> String  
reverseWords = unwords . map reverse . words

I understand that because functional language like Haskell cannot be based on side effects of functions, some solution had to be invented. In this case it seems that everything has to be wrapped in a do block. I get simple examples, but in this case I really need someone's explanation:

  1. Why isn't it enough to use one, single do block for I/O actions?
  2. Why do you have to open completely new one in if/else case?
  3. Also, when does the -- I don't know how to call it -- "scope" of the do monad ends, i.e. when can you just use standard Haskell terms/functions?
Will Ness
  • 70,110
  • 9
  • 98
  • 181
TheMP
  • 8,257
  • 9
  • 44
  • 73
  • 1
    `do` is just syntactic sugar to avoid having to write overly complex statements with `>>=` and `>>` it works with all monads, and has no special association with IO. You don't have to use a second `do` in the example code(you don't even need the first `do` but the resulting code would be harder to read), you could also write `else putStrLn (reverseWords line) >> main` and it would have the same effect. – Leif Grele Jun 18 '14 at 19:59
  • I've answered a largely similar question here: http://stackoverflow.com/questions/23049409/simple-haskell-code-cant-compile/23050814#23050814 Does that help? The trick is to basically look at the return types of all the functions, and everything will fall into place. – iamnat Jun 18 '14 at 20:12
  • http://stackoverflow.com/questions/2488646/why-are-side-effects-modeled-as-monads-in-haskell?rq=1 has some more explanations. – dfeuer Jun 18 '14 at 20:31
  • 1
    [This](http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html) blog post is an excellent thing to read if you're struggling with monads in general, which seems to be the case. Read with a .hs file and ghci prompt open, so you can do the exercises. – Tayacan Jun 19 '14 at 10:03
  • Thank you a lot for help, good tutorial can explain everything much better to me than just theory. – TheMP Jun 19 '14 at 12:54
  • https://stackoverflow.com/tags/do-notation/info – Will Ness Jul 21 '21 at 14:08

2 Answers2

8

The do block concerns anything on the same indentation level as the first statement. So in your example it's really just linking two things together:

 line <- getLine

and all the rest, which happens to be rather bigger:

 if null line  
  then return ()
  else do
      putStrLn $ reverseWords line  
      main  

but no matter how complicated, the do syntax doesn't look into these expressions. So all this is exactly the same as

main :: IO ()
main = do
   line <- getLine
   recurseMain line

with the helper function

recurseMain :: String -> IO ()
recurseMain line
   | null line  = return ()
   | otherwise  = do
           putStrLn $ reverseWords line
           main

Now, obviously the stuff in recurseMain can't know that the function is called within a do block from main, so you need to use another do.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
8

do doesn't actually do anything, it's just syntactic sugar for easily combining statements. A dubious analogy is to compare do to []:

If you have multiple expressions you can combine them into lists using ::

(1 + 2) : (3 * 4) : (5 - 6) : ...

However, this is annoying, so we can instead use [] notation, which compiles to the same thing:

[1+2, 3*4, 5-6, ...] 

Similarly, if you have multiple IO statments, you can combine them using >> and >>=:

(putStrLn "What's your name?") >> getLine >>= (\name -> putStrLn $ "Hi " ++ name)

However, this is annoying, so we can instead use do notation, which compiles to the same thing:

do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn $ "Hi " ++ name

Now the answer to why you need multiple do blocks is simple:

If you have multiple lists of values, you need multiple []s (even if they're nested).

If you have multiple sequences of monadic statements, you need multiple dos (even if they're nested).

that other guy
  • 116,971
  • 11
  • 170
  • 194