Reputation: 2563
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
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, return
ing 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
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