| I don't think the "Xy monad will taint all your code" stands. I used to think that too, but if you have monadic code M and pure code P, if you need to tie P to M (say at a third callsite C), you just lift the P into the monad at C, and that's it. P stays pure, C of course gets monadic, but that is since it _is_ monadic. Now logging: I guess people overpanic this. There are two separate sides of logging I think: 1) Effect logging: If you want to log effects (going to send the email, etc), you are already in IO, no worries. 2) App logic logging: This is more like debug-logging to verify that you logic works and flows as expected. If you need this in pure code, throw in a Writer monad for logging the stuff (can discard it if not needed). 2a) Eventually you'll get somewhere where you have IO, so you can dump the aggregated logs if you want. 2b) Or just use unsafePerformIO to send to a logging thread (beat me with a stick). As a bonus, for app logging you might use a proper ADT for your log statements instead of string, which is great for testing, and even greater for persisting (in json, protobuf, whatever) and later inspection. |
But I thought about it some more, and to me it seems that actually parametrizing the functions to outside world is not that bad; it's a kind of dependency injection, and seems fine. What is really problematic is returning all the IOs (or other monads) from them; especially since you cannot curry return parameters just like you can entry parameters. So even if that could be replaced by some other mechanism, it would be helpful.
But I didn't know unsafePerformIO, sounds like it can be helpful in some cases.