Please Buy now at Leanpub
Written and illustrated by GetContented.
Published on 2016-08-14.
Covers: basic input, simple
do blocks, simple function application, simple definitions.
We’re going to teach you just enough IO that you can write beginning programs. More will come later as we need it to do more capable programs.
We’ll begin straight away on the guided program building.
Task: Make a program to ask for a name, then say hello to that person using their name.
Taking this problem apart, there seem to be three pieces: Display a request for their name, get the name, and finally print the hello message. Let’s begin with the piece we can do most easily: display the message to ask for a name. This is simply printing a string on the screen, which we know how to do:
main :: IO () main = putStrLn "What is your name?"
So we’ve solved a third of the problem. Next, we need to actually get the name of the user. If we think of the functions we know about, the
getLine function is the obvious contender here, which has the type
IO String. We’re going to have to compose this with our
putStrLn action. By now, handily, we know about
do syntax which lets us combine multiple
IO actions into one.
You can think of each expression in an
IO do block as a piece of the composed
IO action, which is exactly what it is. You can write any kind of expressions you like in that
do block, so long as it will evaluate to
As we’ve seen, there are two exceptions to this rule, and they are
do-block let notation, which is used for definitions of pure expressions, and the
<- syntax, which is used for defining variables as the “inner values” of
IO actions that we want to connect to some other part of the rest of the code in our
So, right away, we notice
getLine’s type isn’t what we need it to be. It’s
IO String, not
IO (). However, we know by now that we can use
<- to “get” a variable representing a pure value out of an
IO action so long as the code is still in
do block is an
IO action, so we can do it here. Let’s add the
do block and then use
getLine to define a variable called
theirName as the
String value it pulls out. Remember, though, that the last expression in a
do block must be of type
IO (), so for now we’ll use a new function that we haven’t seen called
return to create a value of this type. We’ll use the expression
return () to put
IO and make that the end expression of our entire
main :: IO () main = do putStrLn "What is your name?" theirName <- getLine return ()
return function places a value into an
IO action. It doesn’t have anything to do with returning values from actions, just with putting values into actions. It’s possibly a bad name to have for your understanding at the beginning, so we apologise about that.
The last piece is to print their name out with hello in front. To do this, we’ll need to use the
putStrLn function again, and also the
(++) operator that joins (concatenates) two lists into one.
String is simply a list of
Char as we know, so this will work fine.
Choose your variable names wisely! Good naming is one of the most difficult tasks in programming. It can help or hinder future readers of your code, including yourself. Names should always help to explain your code as much as possible.
Notice in the next piece of code that we don’t need to have the
return () function application now because of
putStrLn. However, we are using brackets around the application of
(++) to its arguments because if we didn’t,
putStrLn would just take the one
String as its argument, and
(++) would be being applied to an
IO () value on the left side rather than a
main :: IO () main = do putStrLn "What is your name?" theirName <- getLine putStrLn ("Hello, " ++ theirName)
This program is just about the simplest possible program that we could write that uses both input and output in Haskell. If you’d like to, try playing with it a little, by changing to different
String values. Don’t get discouraged if things go wrong.
Task: Write a program that asks you for your name, then your pet’s name, then tells you the info back.
This is still a very simple program, so we’ll just use what we know about
getLine to build our program:
main :: IO () main = do putStrLn "What is your name?" theirName <- getLine putStrLn "What's your pet's name?" petName <- getLine putStrLn ("Your name is " ++ theirName ++ " and your pet's name is " ++ petName)
As we’ve said before, it’s very important to get your indentation correct in Haskell. If you don’t, Haskell won’t know what you mean, and will often complain with strange sounding errors. The indenting is good because it stops us from having to use a lot of bracketing, because Haskell can use the indenting to work out what you mean.
You should experiment and try out different indentations when you have a program that compiles to see what works and what doesn’t, and to get familiar with some of the errors that appear when things go wrong.
The above program is almost the same as the first one, except this time we’re extracting two variables from the user using
getLine. Notice that you can chain operators if they take two of the same type of arguments (such as
(++)) and then the result of the first application will be fed in as the first argument to the second operator, as you would expect it to work.
Let’s look at a different way to write a similar program, which may surprise you a little.
Task: Write a program to ask for the time, then to write “again!” and then ask the user the time again and print out both times.
It’s virtually the same idea for thinking through how to build this program, however we’re going to create a separate action to be re-used in our main action.
Any time you would end up repeating yourself, it’s a great idea to re-use a fragment or value. There’s no reason not to separate it out by making a definition for it. Then you don’t have to repeat yourself! This is one of the core reasons to write computer programs. You should strive to get the computer to do as much of the repetitious work as possible.
whatTimeIsIt :: IO String whatTimeIsIt = do putStrLn "What time is it now?" getLine main :: IO () main = do timeString <- whatTimeIsIt putStrLn "Again!" timeString2 <- whatTimeIsIt putStrLn ("Ok, you said it was " ++ timeString ++ " and then you said it was " ++ timeString2)
whatTimeIsIt is an
IO String action. It has the same type as
getLine, so we can treat it exactly the same. Looking at it, you can see
getLine is the last value in its
do block, so it’s just using a
do block to connect outputting a question up with asking the answer, and returning that string (in
IO, of course).
Here we see the difference between using action results and pure expressions very clearly. We can use the action twice to get two potentially very different values out. We do so using
<- again, and set two different variables to their answer.
Then, we just output the result sentence. All of this is joined up using a do block as before. Nice!
IO action is, is a description of how to enact some execution of code later on. The
<- syntax doesn’t actually “get” anything out of anything else. You can think of it like it does, but what it really does is tells Haskell what to do with action values to combine them. It tells Haskell how to connect up the producing-part of a piece of code that queries the user for input to the rest of the consuming-part of the program, for example.
When we write
do blocks, all we’re really doing is telling Haskell what relationship the smaller pieces should be in compared to each other, and then when the program is executed, things happen in the right order. This will become clearer with more practice, and in later volumes.
Now it’s your turn. When you do these exercises, do them without looking at the supporting explanation documents in this lesson. If you have to look, you can, but mark the exercises you had to look for, and re-do them again after you’ve finished doing them all. You should do them again the next day and so on trying without looking until you can easily do them without any problem at all. Make up some programs of your own that involve asking questions and replying with the inputted data. Don’t try to do anything more complicated than this, yet. You can do this. Let’s go.
Task: Write a program to print a welcome on the screen.
Task: Write a program to ask for someone’s last name, then print it out on the screen.
Task: Write a program that asks two personal questions about the user, and tells their responses back.
Task: Write a program that says it’s going to ask the user to pick three numbers, then uses a separate
IO String action that prompts a user to pick a number, then does it again two more times, then tells them the numbers they picked (note we’re not actually using numbers here, just
String input and output).
Task: Write a program to ask the user where they live, then write a message saying something telling them you’ve heard that’s a great place to live, using the place name in a sentence.
Wow, we’ve reached the end of this Volume together. Well done for getting this far. You’re probably thinking something like "but... we only just got to the part where we’re doing exercises!" Yes, you’re right, but don’t worry. The next volume is where things start getting much more exercise-driven, because now you know enough to start to explore and anchor your understanding of the language by writing code more.
We hope to see you there, and that you let us know if you’ve enjoyed this, tweet about it, tell your friends, and give us feedback!
If you’ve enjoyed reading this, please consider purchasing a copy at Leanpub today
Please follow us, and check out our videos Follow @HappyLearnTutes
Also, Volume 2 is now in beta and being written! Show your support and register your interest at its Leanpub site.