
The real problem is that lazy I/O injects side effects into the pure world of expressions. Haskell has a perfectly good system for encapsulating side effects - the IO monad. So why put these sneaky side effects into pure values?
I fear one problem is that the word "side effect" is not properly defined.
Actually both getContents and interact are in the IO monad. Since the action "interact" is not terminated until the input stream ends, I do not see any problem there.
Yes, it is probably true that you cannot observe any non-referential-transparency using only interact. The side effects are only visible in the IO monad, and since as you say interact doesn't let you back into the IO monad until it has finished, it is probably safe. The unsafety is only visible using the other lazy I/O operations, principally hGetContents.
Evaluation never leaves the IO monad until termination. getContents is more strange. However, you can just define its semantics to yield a random string. When you are not careful you may certainly get something nearly random ;-)
So... you agree that getContents in its current form should really be called unsafeGetContents? Unless perhaps we redefine its semantics to either (a) yield a random string or (b) eagerly slurp the entire stream contents? Cheers, Simon