Okay. I know. I know. The title alone probably caused 40 of you to preemptively groan. I saw the groan counter hit 12 before I even finished the first paragraph last time I brought this up. So: I'm asking for five minutes. Read the whole post. Then groan. Deal?
Let me be clear about what I'm not defending: I'm not saying monad transformer stacks are the future. I'm not saying you should use StateT (ReaderT (WriterT (ExceptT AppError IO))) a for your new greenfield project and feel smug about it. I am saying they are underrated, misunderstood, and often the right tool for a specific class of problem — and that the Haskell community's wholesale abandonment of them in favor of effect systems involves some motivated reasoning we should be honest about.
Here's my thesis in three parts:
1. ReaderT/StateT/EitherT stacking is appropriate when your effect set is small, known, and stable.
The killer use case for polysemy or effectful is when you need to swap out interpretations at the boundary — testing vs. production, logging vs. no-logging, etc. But if your app genuinely just needs config (ReaderT), mutable state (StateT), and error handling (ExceptT), and you know that will not change? The stack is not boilerplate. It is the model. A 3-layer stack is 3 lines of type alias. The "quadratic instance problem" doesn't bite you until you're at 5+ effects, which is a code smell anyway.
2. The "monad transformers compose badly" argument is mostly about the degenerate cases.
Yes, WriterT has well-known space leak issues. Yes, if you stack two ReaderTs, the MTL functional dependencies will bite you — ask will only see the outermost one. These are real problems. But they're known problems with known workarounds, and they don't apply to 80% of usage. The argument "transformers are bad because WriterT leaks" is like saying "IO is bad because you can write infinite loops." Technically true. Not really the point.
3. Effect systems trade one set of problems for another.
Polysemy is beautiful. I've written polysemy code. I love the interpreter pattern. But let's be honest: the compile times are rough, the error messages can be cryptic, and the "you can swap interpreters" promise is mostly useful in tests (which you could also get with ReaderT + typeclass). Effectful is better on performance since it's essentially ReaderT over IO underneath anyway, but then you're kind of just doing... what we were doing before? With extra steps?
I'm not trying to start another flame war. I want an actual discussion. Where do you draw the line? When does a transformer stack become genuinely unworkable versus "aesthetically displeasing"?
nya~ 🐾
nya~ :: AppM nya~