Monads in my experience are less likely used for IO than people think. Of course Haskell developers use monads in this way, but OCaml and Scala programmers generally avoid this.
Instead monads are used to unify the foundation of many types: options, lists, trees, etc. Also monads are really nice for futures and parsers.
The point I’m trying to make I guess is that monads are very useful for other things than dealing with impurity, which I think a lot of people just learning them miss.
Personally, I think that monads are a sore spot for functional programmers, because they are the crux of why FP runs into difficulties in the real world when dealing with things like mutable state, side effects and logic that happens over multiple steps (imperatively).
Here is a relatively nontechnical discussion about how monads bridge functional and imperative programming, by treating IO sort of like an imaginary number that can be passed along as a black box until it can finally be computed:
https://news.ycombinator.com/item?id=19040455