Day 1: IO — Our Ugly Friend

Ben Clifford
Twelve Monads of Christmas
2 min readDec 8, 2016

Here’s a program:

main = do
text <- getLine
putStr "Your name is: "
putStrLn text

We can run it like this:

$ ./One
Ben
Your name is: Ben

It’s easy to think of this program as having three steps (and it does) — but the whole program is represented by a single value of type IO () :

> :t main
main :: IO ()

We could use a different expression of type IO () as a main function, instead of this do block. For example, putStrLn “Hello" has the right type and we could just write this:

main = putStrLn "Hello"

Or we could do something complicated-looking like this:

main = snd (head l)l = [(3, a >>= b), undefined, (8, void $ return 7)]a = getLine
b = fst (putStrLn, putStrLn)

All that matters is that we somehow constructed a value of type IO () to use as our main.

do notation is syntactic sugar — the compiler desugars a do block to a lower level form that uses the >>= (bind) operator, which is defined as part of the Monad typeclass and so varies in behaviour depending on which Monad you are working with.

(>>=) :: Monad m => m a -> (a -> m b) -> m b

Back to our first program:

main = do
text <- getLine
putStr "Your name is: "
putStrLn text

This will desugar to something like this:

main = getLine
>>= \text -> putStr "Your name is: "
>>= \_ -> putStrLn text

I’m going to desugar just a little bit so that we can see a single bind operation:

main = first_thing >>= rest_of_programfirst_thing :: IO String
first_thing = getLine
rest_of_program :: String -> IO ()
rest_of_program text = do
putStr “Your name is: “
putStrLn text

We can run the first thing at a prompt:

> first_thing 
Ben
“Ben”

and we can manually feed the returned value into the rest of the program, emulating >>=:

> rest_of_program “Ben”
Your name is: Ben

We just emulated the IO version of >>=. One of the important things to take on board, I think, is that >>= is “in charge”. Yes, we’ve got a first thing to do, and yes, we’ve got the rest of the program, but those are just arguments being passed to >>=, and it gets to use those arguments however it wants (although hopefully respecting the monad laws).

In IO, >>= makes an new IO action that when you run it, runs the left hand operation, passes the result to the right hand side and runs that operation— but that won’t always be the case with other Monads and that’s part of what I’d like to explore over the next posts.

--

--