I think you are conflating the IO monad with monads in general. For example a stateful idom like x++ in your foo example could be simulated with a state monad, but that does not have anything to do with IO, and would certainly not require the IO monad as you seem to suggest.
You can encapsulate the use of a state monad so you can indeed return a pure int even if you use a state monad inside the function. It is only the IO monad which (for obvious reasons) cannot be encapsulated. So only the IO monad is "contagious" in the way you describe. This is only a problem for you if you have input and output spread all over your program.
You can encapsulate the use of a state monad so you can indeed return a pure int even if you use a state monad inside the function. It is only the IO monad which (for obvious reasons) cannot be encapsulated. So only the IO monad is "contagious" in the way you describe. This is only a problem for you if you have input and output spread all over your program.