A truly lazy IO in Haskell

Let's consider a fragment -

getLine >>= \_ -> getLine >>= putStr 

This does the smart thing by querying for a string twice, and then prints the last input. Since the compiler does not know what the external effects of getLine , it must execute both of them, even if we getLine result of the first.

I need to wrap IO Monad in another Monad (M), which allows I / O calculations to be effectively NOP if their return values ​​are not used. So that the program above can be rewritten somehow

 runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr 

Where

 runM :: M a -> IO a lift :: IO a -> M a 

And the user is prompted to enter only once.

However, I cannot figure out how to write this Monad to achieve the effect that I want. I am not sure if this is possible. Can anybody help?

+6
source share
3 answers

Lazy IO is usually implemented using unsafeInterleaveIO :: IO a -> IO a , which delays the side effects of an I / O action until its result is achieved, so we will probably have to use this, but try some minor problems first .

First of all, lift putStr will not enter a check because putStr is of type String -> IO () , and lift is of type IO a -> M a . Instead, we need to use something like lift . putStr lift . putStr .

Secondly, we will need to distinguish between IO actions that should be lazy and those that should not. Otherwise, putStr will never be executed, since we are not using its return value () anywhere.

Given this, 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 probably shouldn't use this for anything serious. Lazy IO frowned already, as this makes it difficult to reason about the actual order of side effects. That would make it even harder.

Consider this example

 main = runM $ do foo <- lazy readLn bar <- lazy readLn return $ foo / bar 

The reading order of the two numbers will be completely undefined and may vary depending on the compiler version, optimization or alignment of stars. The name unsafeInterleaveIO long and ugly for a good reason: to remind you of the dangers of its use. It is a good idea to let people know when they are being used, rather than hiding them in the monad.

+11
source

There is no reasonable way to do this, because, frankly, this is not a very reasonable thing. The whole purpose of introducing monadic input-output was to give a clearly defined order of influence in the presence of a lazy assessment. Of course, you can throw this window out if you really do, but I'm not sure what problem this problem could solve, besides it was easier to write confused code with an error.

So introducing these kinds of things in a controlled way is what Lazy IO is already doing. The primitive operation for this is unsafeInterleaveIO , which is implemented approximately as return . unsafePerformIO return . unsafePerformIO as well as some details to make things more enjoyable. Applying unsafeInterleaveIO to everything, hiding it in the binding operation of your "lazy IO" monad will probably fulfill the careless opinion that you are after.

+8
source

What you are looking for is not really a monad unless you want to work with insecure things like unsafeInterleaveIO .

Instead, a much more abstraction here is an arrow.
I think the following might work:

 data Promise ma = Done a | Thunk (ma) newtype Lazy mab = Lazy { getLazy :: Promise ma -> m (Promise mb) } 
+5
source

Source: https://habr.com/ru/post/895871/


All Articles