I am still waiting patiently for the light to come on and to have a fully clear understanding of Monads. But I am making progress. When I arrive, I will get busy and write my own explanation of Monads. In fact there is every chance that this will evolve into just that.
A higher order function is simply a function that operates on functions. It accepts function as parameters and/or returns functions as a result. Monads are a high level abstraction of important things you end up wanting to do when you are working with higher order functions. In other words Monads implement plumbing involving higher order functions. Clever people realized they were writing the same sort of code over and over and abstracted it into what are now called Monads. This saves lots of work once you understand what is going on.
Monads are not all about IO. In fact being too hung up about understanding Haskell IO can get in the way of understanding Monads. Understand Monads first, then take a look at Haskell IO. But don't let understanding Monads get in the way of using Haskell IO in the meantime.
The Haskell "do" statement is an escape into a special built in language (or maybe we should say language syntax) for dealing with monads. So, "do" is useful for things other than IO - it works with any monad. Notice that you have to use the "let" word to introduce any statement that does not yeild a result that is an instance of the monad in question. In the case of IO, where we call the IO monad an "action", each statement in the "do" must be an action or a let statement.
The abstract definition of a Monad has 3 parts:
Understanding the chaining operator is the heart of the matter. This is hidden and implicit in the Haskell "do" statement. But only the monadic actions can be chained, hence the "let" is required to indicate those lines that can't be chained.
Tom's Computer Info / email@example.com