Introduction to Haskell IO/Actions
When we're programming in Haskell and we want to do something that has a
side effect, something that affects the world in some way, we use actions.
Actions are values in the Haskell language, much like the number three, the
"hello world", or the function
map. They can be bound to variable
names, passed into a function as an argument or be the result of a function.
Like all other Haskell values, every action has a type. There are many kinds
of actions but we'll start with a very important one called an IO action.
These are the actions that can change the world outside of the programming.
Here are some examples of IO actions:
- Print the string "hello" to the console.
- Read a line of input from the console.
- Establish a network connection to www.google.com on port 80.
- Read two lines of input from the terminal, interpret them as numbers, add them together and print out the result.
- A first-person shooter game that uses mouse movements as input and renders graphics to the screen.
As you can see, IO actions range from the very simple (printing a string) to very comlex (a video game). You may have also noticed that IO actions can also result in a value that can be used by the Haskell program. The point of reading a line of input from the console is to provide data to the program. The type of an action reflects the kind of action (IO) as well as the type of value that it provides as a result (for example String). We say that the action that reads a line of input from the console has the type IO String. In fact, all IO actions will have a type IO a for some result type a. When an action doesn't provide any useful data back to the program the unit type (written ()) is used to denote the result. For programmers familiar with C, C++ or Java, this is similar to the return type of "void" in those languages. The IO actions mentioned above have the following types:
- Print the string "hello" to the console: IO ()
- Read a line of input from the console: IO String
- Establish a network connection to www.google.com on port 80: IO Socket
- Read two lines of input from the terminal, interpret them as numbers, add them together and print out the result: IO Int
- A first-person shooter game that uses mouse movements as input and renders graphics to the screen: IO ()
While actions can result in values that are used by the program, they do not take any arguments. Consider putStrLn. It has the following type:
putStrLn :: String -> IO ()
PutStrLn takes an argument, but it is not an action. It is a function
that takes one argument (a string) and returns an action of type IO ().
So putStrLn is not an action, but
putStrLn "hello" is. The distiction is
subtle but important. All IO actions are of type IO a for some type a.
They will never require additional arguments, although a function which
makes the action (such as
Actions are like directions. They specify something that can be done. They are not active in and of themselves. They need to be "run" to make something happen. Simply having an action lying around doesnt make anything happen. For example,
putStrLn "hello" is an action
in haskell that prints the line "hello". It
IO (). We can write a Haskell program that contains the definition
x = putStrLn "hello"
but that doesn't cause the haskell program to print out "hello"! Haskell
only runs one IO action in a program, the action called main. This
action should have the type
IO (). The following haskell program
will print out "hello":
module Main where main :: IO () main = putStrLn "hello"
You may be wondering how any Haskell program can do anything useful if it can only run a single IO action. As we saw earlier, IO actions can be very complex. Complicated actions can be built up of many simpler actions. We will see how shortly.