There may have been some misconceptions due to the way I started this series in part one. The concept of monads is not the key to performing IO in a purely functional way, they are what makes it practical. IO in Haskell is performed by the IO System, which performs one big IO Action (Main.main) when the Program is run. This IO Action is build up of smaller IO Actions, much like a pure function can be build out of smaller functions. IO Actions are represented by the IO Type.

A value of type IO a is an IO Action that, when performed, does some IO and returns a value of type a. The IO type is completely abstract, so there is no way to create a value of type IO a in pure code. You have to build it out of other values by using the power of monads.