Haskell/Simple input and output
Back to the real world
So far, we have discussed many examples of functions that calculate values. Of course, we also want to use our programs for other things. For example, the standard program in the beginning of tutorials about other languages: a program that displays a "hello world" greeting. Here's one Haskell version:
Prelude> putStrLn "Hello, World!"
putStrLn is one of the standard Prelude functions. As the "putStr" part of the name suggests, it takes a
String as an argument and prints it to the screen. The "Ln" indicates it also prints a line break, so that whatever else is printed next will appear on a new line.
So now you should be thinking, "what is the type of the putStrLn function?" It takes a
String and gives… um… what? What do we call that? The program doesn't get something back that it can use in another function. Instead, the result involves having the computer change the screen. In other words, it does something in the world outside of the program. What type could that have? Let's see what GHCi tell us:
Prelude> :t putStrLn putStrLn :: String -> IO ()
"IO" stands for "input and output". Wherever there is
IO in a type, interaction with the world outside the program is involved. We'll call
IO values, such as the result of
putStrLn, actions. The other part of the
IO type, in this case
(), is the type of the return value of the action; that is, the type of what it gives back to the program (as opposed to what it does outside the program).
() (read as "unit") is an uninteresting type with just a single value, also called
putStrLn sends output to the world but doesn't return anything to the program,
() is used as a placeholder. We might read
IO () as "action which returns
()", just like we read
[Int] as "list of
Here are just a few examples of when we need to use IO:
- print a string to the screen
- read a string from a keyboard
- write data to a file
- read data from a file
What makes IO actually work? Lots of things happen behind the scenes to take us from a
putStrLn to pixels in the screen; however, we don't have to worry about them right now. What we have to know is that a complete Haskell program is actually a big IO action that is run when the program is executed. In a compiled program, this action is called
main, and has type
IO (). From this point of view, to write a Haskell program is to combine actions and functions to form the overall function
main that will be executed when the program is run.
|Back in the Type Basics chapter, we mentioned that the type of the
Sequencing actions with do
do notation provides a convenient means of putting actions together, which is essential in getting useful things done with Haskell. Let's see what it looks like by considering a more complex program:
Example: What is your name?
main = do putStrLn "Please enter your name: " name <- getLine putStrLn ("Hello, " ++ name ++ ", how are you?")
Before we get into how do works, take a look at
getLine. It goes to the outside world, in this case to the terminal, and brings back a
String from it. What is its type?
Prelude> :t getLine getLine :: IO String
getLine is an IO action that, when run, will return a
String. But what about the input? While functions have types like
a -> b which reflect that they take arguments and give back results,
getLine doesn't actually take an argument. It takes as input whatever is in the line in the terminal. However, that line in the outside world can't be a value with a type because it isn't even in Haskell yet! The Haskell program can't know in advance what it will be, and indeed the value could be different every time.
As there is no way to predict the exact results of IO actions (since the program doesn't know the state of the outside world until runtime), they have to be executed in a predictable sequence defined in advance in our code. With regular functions that do not perform IO, the exact sequencing of execution is less of a concern as long as the results eventually go to the right places.
In our name program, we're sequencing three actions: a
putStrLn with a greeting, a
getLine and another
putStrLn. With the
getLine, we use
<- notation, which is a way to get the return value out of an action, so that we can use it elsewhere (in this case, to prepare the final message being printed). The final action defines the type of the whole
do block and, in this case, of the program. Here, the final action is the result of a
putStrLn, and so it has type
Write a program which asks the user for the base and height of a right angled triangle, calculates its area and prints it to the screen. The interaction should look something like:
The base? 3.3 The height? 5.4 The area of that triangle is 8.91Hint: you can use the function
Left arrow clarifications
While actions like
getLine are almost always used to get values, we are not obliged to actually get them. For example, we could very well have written something like this:
main = do putStrLn "Please enter your name: " getLine putStrLn ("Hello, how are you?")
Clearly, that isn't very useful: the whole point of prompting the user for his or her name was so that we could do something with the result. That being said, it is conceivable that one might wish to read a line and completely ignore the result. In real life, we often get someone's name when meeting them and then promptly forget it even though we keep talking… By omitting the
<-, the action will happen, but the data won't be stored anywhere.
It's nicer to actually remember someone's name, of course. So, in order to get the value out of the action, we write
name <- getLine, which basically means "run
getLine, and put the results in the variable called
<- can be used with any action except the last
There are very few restrictions on which actions can have values obtained from them. Consider the following example, where we put the results of each action into a variable (except the last... more on that later):
Example: putting all results into a variable
main = do x <- putStrLn "Please enter your name: " name <- getLine putStrLn ("Hello, " ++ name ++ ", how are you?")
x gets the value out of its action, but that isn't useful in this case because the action returns the unit value
(). So while we could technically get the value out of any action, it isn't always worth it.
So, what about that last action? Why can't we get a value out of that? Let's see what happens when we try:
Example: getting the value out of the last action
main = do x <- putStrLn "Please enter your name: " name <- getLine y <- putStrLn ("Hello, " ++ name ++ ", how are you?")
HaskellWikibook.hs:5:2: The last statement in a 'do' construct must be an expression
This is a much more interesting example, but it requires a somewhat deeper understanding of Haskell than we currently have. Suffice it to say, whenever you use
<- to get the value of an action, Haskell is always expecting another action to follow it. So the very last action better not have any
Normal Haskell constructions like if/then/else can be used within the do notation, but you need to be somewhat careful. For instance, in a simple "guess the number" program, we have:
doGuessing num = do putStrLn "Enter your guess:" guess <- getLine if (read guess) < num then do putStrLn "Too low!" doGuessing num else if (read guess) > num then do putStrLn "Too high!" doGuessing num else putStrLn "You Win!"
If we think about how the if/then/else construction works, it essentially takes three arguments: the condition, the "then" branch, and the "else" branch. The condition needs to have type
Bool, and the two branches can have any type, provided that they have the same type. The type of the entire if/then/else construction is then the type of the two branches.
In the outermost comparison, we have
(read guess) < num as the condition. This clearly has the correct type. Let's just consider the "then" branch. The code here is:
do putStrLn "Too low!" doGuessing num
Here, we are sequencing two actions:
doGuessing. The first has type
IO (), which is fine. The second also has type
IO (), which is fine. The type result of the entire computation is precisely the type of the final computation. Thus, the type of the "then" branch is also
IO (). A similar argument shows that the type of the "else" branch is also
IO (). This means the type of the entire if/then/else construction is
IO (), which is just what we want.
Note: be careful if you find yourself thinking, "Well, I already started a do block; I don't need another one." We can't have code like:
do if (read guess) < num then putStrLn "Too low!" doGuessing num else ...
Here, since we didn't repeat the do, the compiler doesn't know that the
doGuessing calls are supposed to be sequenced, and the compiler will think you're trying to call
putStrLn with three arguments: the string, the function
doGuessing and the integer
num, and thus reject the program.
Write a program that asks the user for his or her name. If the name is one of Simon, John or Phil, tell the user that you think Haskell is a great programming language. If the name is Koen, tell them that you think debugging Haskell is fun (Koen Classen is one of the people who works on Haskell debugging); otherwise, tell the user that you don't know who he or she is.(As far as syntax goes there are a few different ways to do it; write at least a version using
Actions under the microscope
Actions may look easy up to now, but they are actually a common stumbling block for new Haskellers. If you have run into trouble working with actions, you might consider looking to see if one of your problems or questions matches the cases below. It might be worth skimming this section now, and coming back to it when you actually experience trouble.
Mind your action types
One temptation might be to simplify our program for getting a name and printing it back out. Here is one unsuccessful attempt:
Example: Why doesn't this work?
main = do putStrLn "What is your name? " putStrLn ("Hello " ++ getLine)
HaskellWikiBook.hs:3:26: Couldn't match expected type `[Char]' against inferred type `IO String'
Let us boil the example above down to its simplest form. Would you expect this program to compile?
Example: This still does not work
main = do putStrLn getLine
For the most part, this is the same (attempted) program, except that we've stripped off the superfluous "What is your name" prompt as well as the polite "Hello". One trick to understanding this is to reason about it in terms of types. Let us compare:
putStrLn :: String -> IO () getLine :: IO String
We can use the same mental machinery we learned in Type basics to figure how this went wrong. Simply put, putStrLn is expecting a
String as input. We do not have a
String, but something tantalisingly close, an
IO String. This represents an action that will give us a
String when it's run. To obtain the
putStrLn wants, we need to run the action, and we do that with the ever-handy left arrow,
Example: This time it works
main = do name <- getLine putStrLn name
Working our way back up to the fancy example:
main = do putStrLn "What is your name? " name <- getLine putStrLn ("Hello " ++ name)
Now the name is the String we are looking for and everything is rolling again.
Mind your expression types too
So, we've made a big deal out of the idea that you can't use actions in situations that don't call for them. The converse of this is that you can't use non-actions in situations that expect actions. Say we want to greet the user, but this time we're so excited to meet them, we just have to SHOUT their name out:
Example: Exciting but incorrect. Why?
import Data.Char (toUpper) main = do name <- getLine loudName <- makeLoud name putStrLn ("Hello " ++ loudName ++ "!") putStrLn ("Oh boy! Am I excited to meet you, " ++ loudName) -- Don't worry too much about this function; it just capitalises a String makeLoud :: String -> String makeLoud s = map toUpper s
This goes wrong...
Couldn't match expected type `IO' against inferred type `' Expected type: IO t Inferred type: String In a 'do' expression: loudName <- makeLoud name
This is similar to the problem we ran into above: we've got a mismatch between something that is expecting an IO type, and something which does not produce one. This time, the cause is our use of the left arrow
<-; we're trying to left arrow a value of
makeLoud name, which really isn't left arrow material. It's basically the same mismatch we saw in the previous section, except now we're trying to use regular old String (the loud name) as an IO String, when those clearly are not the same thing. The latter is an action, something to be run, whereas the former is just an expression minding its own business. We cannot simply use
loudName = makeLoud name because a
do sequences actions, and
loudName = makeLoud name is not an action.
So how do we extricate ourselves from this mess? We have a number of options:
- We could find a way to turn
makeLoudinto an action, to make it return
IO String. However, we don't want to make actions go out into the world for no reason. Within our program, we can reliably verify how everything is working. When actions engage the outside world, our results are much less predictable. An IO
makeLoudcould be made somehow, but that would be misguided. Consider another issue though: what if we wanted to use makeLoud from some other, non-IO, function? We really don't want to engage IO actions except when absolutely necessary.
- We could use a special code called
returnto promote the loud name into an action, writing something like
loudName <- return (makeLoud name). This is slightly better, in that we are at least leaving the
makeLoudfunction itself nice and IO-free, whilst using it in an IO-compatible fashion. That's still moderately clunky because, by virtue of left arrow, we're implying that there's action to be had -- how exciting! -- only to let our reader down with a somewhat anticlimactic
return(note: we will learn more about appropriate uses for
returnin later chapters).
- Or we could use a let binding...
It turns out that Haskell has a special extra-convenient syntax for let bindings in actions. It looks a little like this:
let bindings in
main = do name <- getLine let loudName = makeLoud name putStrLn ("Hello " ++ loudName ++ "!") putStrLn ("Oh boy! Am I excited to meet you, " ++ loudName)
If you're paying attention, you might notice that the let binding above is missing an
in. This is because
let bindings inside
do blocks do not require the
in keyword. You could very well use it, but then you'd have messy extra do blocks. For what it's worth, the following two blocks of code are equivalent.
do name <- getLine let loudName = makeLoud name putStrLn ("Hello " ++ loudName ++ "!") putStrLn ( "Oh boy! Am I excited to meet you, " ++ loudName)
do name <- getLine let loudName = makeLoud name in do putStrLn ("Hello " ++ loudName ++ "!") putStrLn ( "Oh boy! Am I excited to meet you, " ++ loudName)
At this point, you should have the fundamentals needed to do some fancier input/output. Here are some IO-related topics you may want to check in parallel with the main track of the course.