This is a follow up of my last question. IO action nested in other monads not executing
The solution to that question was to remove some of the monads, and that allowed the IO action to execute.
Why did I need to unnest the monads? Is there a way to execute the IO without unnesting?
Note: This is a what-if more than it is a question about good or bad practice.
Perhaps it would help to think of IO
as type IO a = World -> (a, World)
; that is, a function that takes as its only parameter the current state of your computer and returns a new state along with some value a
. This is not too dissimilar from the actual implementation of IO
in GHC internals, so hopefully we can forgive the (abject) method of communicating by analogy here.
So readFile :: FilePath -> IO String
, for example, becomes readFile :: FilePath -> World -> (a, World)
.
And main :: IO ()
is really main :: World -> ((), World)
.
What this means, however, is that values with type IO _
are inert. They are just functions! Functions cannot do anything until they are given a value; in our case, the value the function wants is a World
object, which we have no way of constructing. Therein lies the beauty of IO in Haskell: we can build up an IO
action by using the monadic operators we know and love (return, bind) but it cannot do anything until the runtime passes in the World
object.
Which means that any IO
action we build that isn't threaded through main
won't be executed.
So, with foobar :: [Char] -> IO [IO ()]
, we can certainly observe the return value:
main :: IO ()
main = do
ios <- foobar "string"
print "goodbye"
But it's not until we deconstruct ios
and bind the internal IO
values that those actions receive the World
they desire:
main :: IO ()
main = do
ios <- foobar
ios !! 0
ios !! 1
ios !! 2
...
print "goodbye"
Or, for short,
main = do
ios <- foobar
sequence ios
print "goodbye"
Hope this helps.
Let's begin with a slightly different example. As you know, a String
is a list of Char
:
GHCi> :set +t
GHCi> "Mississippi"
"Mississippi"
it :: [Char]
A list of Strings
is a list of lists of Char
; that is, a [[Char]]
:
GHCi> group "Mississippi"
["M","i","ss","i","ss","i","pp","i"]
it :: [[Char]]
group "Mississippi"
is a [[Char]]
, and I don't want it handled as a [Char]
-- that would defeat the point of using group
.
An IO a
value is, for most purposes, a value just like any other, and so analogous considerations apply. To give a concrete (and realistic) example, suppose we have a function with this type...
(KeyCode -> IO ()) -> IO (IO ())
... which registers event handlers for a key press event in a GUI. The idea is that you call the function with a KeyCode -> IO ()
argument, which specifies what should happen in response to the key press, and run the the resulting IO (IO ())
so that the your chosen KeyCode -> IO ()
handler becomes active. The inner IO ()
produced by the IO (IO ())
action, however, serves a different purpose: it unregisters the event handler, and it is meant to be used at a later point of the application at your discretion -- perhaps never. In this case, you definitely do not want to run the inner action immediately after the outer one!
Summing it up, an IO (IO a)
is an IO
action that, when run, produces another IO
action, which you may or may not want to run as well.
P.S.: As sheyll mentioned in the other Q&A, join
can be used to flatten a nested IO
action, or any other nested monadic value. Incidentally, lists also have a Monad
instance. What do you think join (group "Mississippi")
will do?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With