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:
do
block for I/O actions?do
monad ends, i.e. when can you just use standard Haskell terms/functions?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
.