While I have a lot of respect for the author, I have to emphatically disagree with a lot of what he says in this article. Using an IORef instead of StateT is a hack, not a sound architectural choice. I think a lot of this comes down to how you view the role of IO in your application stack.
For me, the application logic lives in the transformer stack and IO is just what needs to go at the bottom of the stack if I want to make syscalls. I try to catch exceptions at every use of liftIO and transform them into ExceptT exceptions, and I don’t attempt to use asynchrony within my transformer stack. The example he used of making an asynchronous concurrent operation within StateT struck me as very weird and not something I would ever do in real life, and I’m not sure how one could understand the way StateT works and yet still expect it to do anything besides the three possibilities he mentioned. Maybe this just comes down to different mental models of the various basic transformers.
My transformer stack almost always is some composition of ReaderT, WriterT, and ExceptT (possibly over IO). If I need to do concurrency, I do it explicitly in IO, not inside my stack where the implementation might be non-obvious.
It’s also silly to claim that StateT is “impure”. There’s a world of difference between expressing a pure algorithm in a stateful way and a system that actually benefits from real, impure, referentially opaque mutable values.
See also the implicit parameters approach as discussed in the article comments. It seems to be a less boilerplate approach.