You’ve seen a number of examples of I/O in Haskell by this point. Let’s take a moment to step back and think about how I/O relates to the broader Haskell language.
Since Haskell is a pure language, if you give a certain function a specific argument, the function will return the same result every time you give it that argument. Moreover, the function will not change anything about the program’s overall state.
You may be wondering, then, how I/O fits into this picture. Surely if you want to read a line of input from the keyboard, the function to read input can’t possibly return the same result every time it is run, right? Moreover, I/O is all about changing state. I/O could cause pixels on a terminal to light up, cause paper to start coming out of a printer, or even to cause a package to be shipped from a warehouse on a different continent. I/O doesn’t just change the state of a program. You can think of I/O as changing the state of the world.
Most languages do not make a distinction between a pure function and an impure one. Haskell has functions in the mathematical sense: they are purely computations that cannot be altered by anything external. Moreover, the computation can be performed at any time—or even never, if its result is never needed.
Clearly, then, we need some other tool to work with I/O. That tool in Haskell is called actions. Actions resemble functions. They do nothing when they are defined, but perform some task when they are invoked. I/O actions are ...