Search code examples
haskelliomonadsio-monad

What are the consequences of returning an IO action?


In Haskell, the type constructor IO is a monad equipped with a return statement that lifts any expression to its IO version.

Nothing prevents us from lifting what is already an IO action to its IO version - giving us a type of the form IO (IO a).

So I can for example write the following program:

main = return . print $ "Hello world"

Which upon execution does exactly nothing.

My question is, what happens under the hood when this main is executed?

Are there any situations where it makes sense to return an IO action?


Solution

  • IO is often approximated to State RealWorld, that is a State monad that operates on the "real world". return for State produces an action that does not alter contained state. So, by analogy, returning anything to IO doesn't do anything as well. Not only when you return some IO a, but any a.

    Returning an IO action may be used to build up a computation in one place (capturing some values into a closure along the way) and execute it somewhere else. Much like passing a callback in C.

    Actually, to build up an IO computation and pass it somewhere else you can use a let inside of a do-block:

    main :: IO ()
    main = do
      let act = readFile "somefile" -- action is not performed yet
      foo act -- pass the action as a parameter
    
    foo :: IO () -> IO ()
    foo act = do
      .. do something
      result <- act -- actually perform the action
      ..
    

    In this case you don't need to return an IO a value.

    But if the process of building that computation itself requires you to perform IO actions, that's where you'd need such a thing. In our example, let's ask user of filename to be opened:

    main :: IO ()
    main = do
      act <- do
        filename <- getLine
        return (readFile filename)
      foo act
    

    Here, we need a filename to create our action, but getLine is in IO as well. That's where additional level of IO comes out. Of course, this example is synthetic, as you can just do filename <- getLine right in main.