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 putStr
would not type check, as putStr
has type String -> IO ()
, and lift
has type IO a -> M a
. We'll have to use something like lift . putStr
instead.
Secondly, we're going to have to differentiate between IO actions that should be lazy and those who should not. Otherwise the putStr
will 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.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import System.IO.Unsafe
newtype M a = M { runM :: IO a }
deriving (Monad)
lazy :: IO a -> M a
lazy = M . unsafeInterleaveIO
lift :: IO a -> M a
lift = M
main = runM $ lazy getLine >> lazy getLine >>= lift . putStr
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
main = runM $ do
foo <- lazy readLn
bar <- lazy readLn
return $ foo / bar
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 unsafeInterleaveIO
is 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.
There's no sensible way to do this, because to be quite honest it's not really a sensible thing to do. The entire purpose for introducing monadic I/O was to give a well-defined ordering to effects in the presence of lazy evaluation. It is certainly possible to throw that out the window if you really must, but I'm not sure what actual problem this would solve other than making it easier to write confusingly buggy code.
That said, introducing this sort of thing in a controlled fashion is what "Lazy IO" already does. The "primitive" operation for that is unsafeInterleaveIO
, which is implemented roughly as return . unsafePerformIO
, plus some details to make things behave a bit nicer. Applying unsafeInterleaveIO
to everything, by hiding it in the bind operation of your "lazy IO" monad, would probably accomplish the ill-advised notion you're after.
What you are looking for isn't really a monad, unless you want to work with unsafe stuff like unsafeInterleaveIO
.
Instead, a much cleaner abstraction here is Arrow.
I think, the following could work:
data Promise m a
= Done a
| Thunk (m a)
newtype Lazy m a b =
Lazy { getLazy :: Promise m a -> m (Promise m b) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With