For me the block for a long time for monads was understanding how they made IO possible.
That is, the basic ideas for things like a Maybe or similar were obvious and intuitive to me, and everyone software dev for long has done such things, even if they never think about what parts they could refactor out into something reusable.
But the leap from that to how it makes IO possible without side effects I just did not grok. It really only clicked reading something or other that described (admitting it was lying a little bit) the IO monad as something that threaded the state of the world through the program; that is, that world state becomes both an (invisible) input and output for functions, and so effects on it are fully encapsulated by the param(s) and return type of the function.
That's a fair observation and I admit I didn't consider that when I wrote my comment. It is pretty difficult to visualise and leads to a lot of "but you still interact with the outside world so how can it be pure" kinds of confusion. Its very hard to visaulise how the program essentially gets wound up and then at runtime as IO occurs gets unwound. Some of the other monads are definitely simpler to understand!
That is, the basic ideas for things like a Maybe or similar were obvious and intuitive to me, and everyone software dev for long has done such things, even if they never think about what parts they could refactor out into something reusable.
But the leap from that to how it makes IO possible without side effects I just did not grok. It really only clicked reading something or other that described (admitting it was lying a little bit) the IO monad as something that threaded the state of the world through the program; that is, that world state becomes both an (invisible) input and output for functions, and so effects on it are fully encapsulated by the param(s) and return type of the function.