Consider the fragment –
getLine >>= \_ -> getLine >>= putStr
It does the reasonable thing, asking for a string twice, and then printing the last input. Because the compiler has no way of knowing what outside effects getLine has, it has to execute both of them, even though we throw away the result of the first one.
What I need is to wrap the IO Monad into another Monad (M) that allows IO computations to be effectively NOPs unless their return values are used. So that the program above could be rewritten as something like –
runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr
Where
runM :: M a -> IO a
lift :: IO a -> M a
And the user is asked for input only once.
However, I cannot figure out how to write this Monad to achieve the effect I want. I’m not sure if it’s even possible. Could someone please help?
Lazy IO is usually implemented using
unsafeInterleaveIO :: IO a -> IO a, which delays the side effects of an IO action until its result is demanded, so we’ll probably have to use that, but let’s get some minor problems out of the way first.First of all,
lift putStrwould not type check, asputStrhas typeString -> IO (), andlifthas typeIO a -> M a. We’ll have to use something likelift . putStrinstead.Secondly, we’re going to have to differentiate between IO actions that should be lazy and those who should not. Otherwise the
putStrwill never be executed, as we’re not using it’s return value()anywhere.Taking that into account, this seems to work for your simple example, at least.
However, as C. A. McCann points out, you should probably not use this for anything serious. Lazy IO is frowned upon already, as it makes it difficult to reason about the actual order of the side effects. This would make it even harder.
Consider this example
The order of the two numbers are read in will be completely undefined, and may change depending on compiler version, optimizations or the alignment of the stars. The name
unsafeInterleaveIOis long and ugly for a good reason: to remind you of the dangers of using it. It’s a good idea to let people know when it’s being used and not hide it in a monad.