Jsevillamol
Jsevillamol

Reputation: 2563

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?

Upvotes: 3

Views: 134

Answers (2)

arrowd
arrowd

Reputation: 34411

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.

Upvotes: 2

chepner
chepner

Reputation: 532122

Under the hood, the runtime effectively discards the result of the IO action main, which is why it is usually defined as IO (). This means that if main actually has a type like IO (IO Int), there is no real issue. The IO action is executed, and the result (another IO action) is discarded, unexecuted.

Deeper in your program, you are more likely to just trigger a type error. For example, fmap doSomething (return . getLine) won't type-check if you meant fmap doSomething getLine.

Upvotes: 3

Related Questions