Monad Transformers aren't _that_ bad, the MTL style of doing things makes it all pretty painless.
It also provides a huge opportunity for testing. At a very high level, you describe all of your effects as a series of embedded, compostable DSLs that you define interpreters for. The awesome part is that you can switch out the interpreters at will, so you can, for example, replace something that handles network requests with something that returns dummy data almost effortlessly.
Yeah, I guess the `do` notation makes it pretty painless. If you start mixing monads, though, things get hairy quickly.