Which leads to code that needs to be puzzled out, rather than code that flows naturally. 'What does [arbitrary-monad-operationM_] mean for SomeMonadTransformerStack again???' is all too common in my experience.
I really think that's false abstraction, like an 'object' superclass in OO languages.
And once you learn them, it's a lot easier to see what code using them does at a glance, rather than reading the corresponding for loop to work out what it does, and whether it has any side effects.
It's trivial to read a loop, because it's right there. It's trivial to tell if it has any effects, because they're notated right there as algebraic effects.
It means you're mapping over something and collecting the results. Yes you need a little more information to understand exactly what you're mapping over or what "collect" means in this context, but this is still more information that `for` gives you without reading more.
Although I find in practice it's usually fairly obvious what `forM` means in context, because you're likely writing a lot of code working with the same monads.
>they're notated right there as algebraic effects.
I'm not sure what you mean by this. Unless you're using some kind of advanced imperative language with effect types, they're not "right there", you have to infer them from the code.
Whereas with Haskell you usually have a type signature which tells you what the effects are.