Not as an "escape hatch" (that would be something more like `unsafePerformIO :: IO a -> a`), but as a principled way to compose (among other things) IO actions.
A Haskell program executes the IO action at `Main.main`, which must have type `IO ()`.
`putStrLn :: String -> IO ()` is a pure function - if you give it the same input, it always the same IO action as a result.
Monads are (for better or worse) contagious. So when one function calls a monad, then it needs to be included in the monad as well. It makes introducing memoisation to a file, randomisation, and memoisation not-to-a-file (without using lazy evaluation to express it) difficult. I don't know whether the alternatives (effect systems for instance) help with this.
I personally don't use functional languages because I find them too difficult given the needs and interests I have. I think about computations sequentially most of the time.
But then monads are a way to think about computations sequentially. If I write highly sequential code in a C-like language, in many cases most of the code is just boilerplate made necessary by the absence of native support for monads:
int ret = doStepOne();
if (ret == RESULT_OK) {
ret = doStepTwo();
}
if (ret == RESULT_OK) {
ret = doStepThree();
}
return ret;
Separating IO is kind of the killer feature, something like `rand()` is not an int unless you're reading dilbert[1] or xkcd[2]. Basic equations break down in the face of an Int-valued IO-action being conflated with an Int
Monads themselves aren't really contagious, it's the actions that would otherwise have side-effects that are, and also the fact they have to be executed in sequence.
This is also true for other things in other paradigms, such as async functions in javascript.
This is a good thing, however. In imperative programming, you have invisible temporal coupling. In pure-FP you have the same coupling, but it's exposed.
While I broadly agree with you, I think the post you're responding to has a point. You can't, in general, get a value back out of a monad, so if you call a monadic function, you may well have to return a monad. The obvious example is IO: there's no (safe) way get the `a` from `IO a`, so IO is kinda contagious.
Then again, there are lots of monads, such as `Maybe` and `List`, where you can get values out. These aren't contagious at all.
I agree with you that this is a good thing. Effects show up in the type signature - and it's all about those effects and managing them.
I think that applicatives and monads feel more contagious than they really are, because at first people tend to write functions that consume values of type `f a` too readily. This is because it takes some time to become comfortable with `fmap` and friends, so the new Haskell programmer often doesn't write as many pure functions.
A Haskell program executes the IO action at `Main.main`, which must have type `IO ()`.
`putStrLn :: String -> IO ()` is a pure function - if you give it the same input, it always the same IO action as a result.