Axman’s Haskell IO tutorial
June 29th, 2009 by Axman6

So, after a discussion in #haskell, I decided I should write a tutorial for absolute beginners trying to do IO in haskell. One of the biggest problems is that people explain IO in a way that is both 100% true, and very confusing; that IO actions are like programs that get execu… You’re a beginner, you don’t care, you just want to actually be able to write a hello world program, you’ll figure out the rest later (i know I did). So here it is, not that I expect you to learn anything from it:

>    main :: IO ()
>    main = putStrLn "Hello World"
What’s that saying? Well print Hello world to the terminal, whoop dee doo. So on to the real learning. Let’s look at putStrLn for a sec, it has the type:
>    putStrLn :: String -> IO ()
What this is saying is that putStrLn takes a string, and gives you nothing back, which is cool, what would you want back from printing something?

Now, one of the things people get confused about is do notation. Beginners would like to be able to just do something like

>    echo = putStrLn getLine
So let’s look at this function. GetLine has the type
>    getLine :: IO String
And putStrLn
>    putStrLn :: String -> IO ()
Can you see the problem? We’re trying to put an IO String where a String is supposed to be. We need to get that string out! How do we do this? Well, think of IO a as being a box with an a inside it. a might be anything, a String, an Integer, a list of FilePaths, these would have the types IO String, IO Integer, IO [FilePath]. To be able to do anything with these, we need to take them out of the box, this is where do notation comes in. Let’s have a look at a working version of the above function echo:
>    echo :: IO ()
>    echo =
>    do
>        str <- getLine
>        putStrLn str
What happened there? Well the first thing was str <- getLine, doing this took the string out of the IO String that is getLine. Then on the next line, we printed out the string we took out of the box.

I should probably explain the type signature of echo. With do notation, the result of the function is whatever the last line in the do block gives back. Since our last line is is putStrLn str, and it has the type IO (), that’s the result of our function.

So what if we want to get something back from our function, not just simple nonsense like echo? Well let’s take small steps here. First, well write a function ask:

>    ask :: String -> String -> IO ()
>    ask str1 str2 =
>        do
>            putStr str1
>            x <- getLine
>            putStrLn (str2 ++ x)
What’s this so? Well, you give it two strings, a question or something, doesn’t really matter, print that out, and something you’ll stick in front of whatever you read in, and then print that out. (PutStr is like putStrLn, but without the new line after, nice for prompts like this) So, using it, we would do something like (Oh so clich├ęd!):
>    main = ask "How, you doin', What's ya name? " "Oh, I'm so sorry about that "
Which would look like this when run:
    How you doin', What's ya name?  Axman!
    Oh, I'm so sorry about that Axman!
That’s nice ‘n all, but you want to actually do stuff with these function, right? Well let’s get to it! Let’s write something that well give us an Integer back:
>    getInteger :: IO Integer
>    getInteger =
>        do
>            putStr "Enter an integer: "
>            readLine :: IO Integer -- This type signature isn't necessary,
>                                   -- I just put it there to help you, 
>                                   -- so don't complain! 
This will work great, but it’s deceptively simple, so let’s expand it:
>    getInteger :: IO Integer
>    getInteger =
>        do
>            putStr "Enter an integer: "
>            line <- getLine
>            return (read line)
That’s all well and good, you should understand the first two lines, so I won’t explain them. The last line might look ok, but it’s important you understand what it’s doing. In the above code, getLine returns an IO String, then we use read to convert that string into an integer (maybe not the safest way, but this is an IO tutorial, not a read tutorial. If you’re interested, take a look at reads). So, after applying read to the string we have an Integer, but our function has the type IO Integer, how do we get there? We use return! basically, return puts the integer inside the box.

Why do we want to do that? Why can’t you just make the last line read line? Because we have to, just accept that for now. When we start working in IO, we’re kind of stuck in IO. This isn’t a bad thing, just a little hard to get used to. “Does this mean all my functions need to be IO functions???” I hear you ask in horror. Well no. You’ve already seen how to take things out of the box, and even work with them, and then put them back in the box. read in the above code isn’t an IO function, but we still used it just fine. We took that line out of the box, read it into an Integer, and boxed that back up.

So, how do we use this boxed up Integer? How about like this:

>    main =
>        do
>            int <- getInteger
>            print (2^int)
Here we used the function we defined (!) to get an Integer and print out two to the power of it. (print x is basically short for putStrLn (show x), it’ll print out the string that show returns, so it can print out anything with a Show instance). Want an example of how you can use your own functions? you got it!
>    main =
>        do
>            x <- getInteger
>            let ans = fac x
>            print ans
>    fac n | n < 2 = 1
>          | otherwise = n * fac (n-1)

One Response  
  • Lilly writes:
    December 15th, 2013 at 4:48 PM

    Awesome article! I learned a bunch!

Leave a Reply

»  Substance: WordPress   »  Style: Ahren Ahimsa
© Alex Mason (Axman6) 2009