Lazy IO with recursive reads?

Hi all, I am trying to read structured data from a socket and return a lazy list of records. However, the socket reading operation seems to be strict and never returns (until stack overflow). Here's some simplified code to reproduce the problem: -------------------- import Control.Monad main = do messages <- readLazy mapM_ (\x -> putStr $ show x ++ "\n") $ messages return () where readLazy :: IO [String] readLazy = do c <- fancyIORead liftM2 (++) (return c) readLazy fancyIORead :: IO [String] fancyIORead = return ["aa","bb"] -------------------- In my implementation fancyIORead reads blocks from the socket and returns a list of records. But it seems readLazy doesn't ever return. What could be the problem here? Also, if anyone has a better solution to write this thing, pls let me know. Thanks! Fabian

On Wed, Feb 24, 2010 at 1:53 PM, Fabian Roth
Hi all, I am trying to read structured data from a socket and return a lazy list of records. However, the socket reading operation seems to be strict and never returns (until stack overflow).
This is expected behaviour. Normal sequencing of IO actions is done in such a way as to preserve their order, which is obviously pretty important if you want to ask for a response to your message after you've sent it, rather than before. Lazy IO operations violate that order and as a result are pretty scary and usually to be avoided. In general, laziness only works well with pure functions where the order doesn't matter because there are no observable side-effects. There are ways of making IO lazy, but there are pretty much invariably other ways of doing the same thing which result in fewer headaches later on. I am hoping that other people more educated than I am will be able to tell you about Iteratees and so forth.

Hi Fabian You need to yield with unsafeInterleaveIO to allow some of the list to be be consumed. Something like this (which never terminates of course, but do produce output): import System.IO.Unsafe import Control.Monad main = do messages <- readLazy mapM_ (\x -> putStr $ show x ++ "\n") $ messages return () where readLazy :: IO [String] readLazy = unsafeInterleaveIO $ do { c <- fancyIORead ; liftM2 (++) (return c) readLazy } fancyIORead :: IO [String] fancyIORead = return ["aa","bb"]

Hi Stephen
Thank you very much, this indeed does the trick!
Using UnsafeIO, however, leaves a creepy unsafe feeling...
I don't fully understand though why it is unsafe. Doesn't hGetContents do
the exact same thing (i.e. reading from IO returning a lazy string) but does
not require UnsafeIO.
Fabian
On Wed, Feb 24, 2010 at 4:38 PM, Stephen Tetley
Hi Fabian
You need to yield with unsafeInterleaveIO to allow some of the list to be be consumed.
Something like this (which never terminates of course, but do produce output):
import System.IO.Unsafe import Control.Monad
main = do messages <- readLazy mapM_ (\x -> putStr $ show x ++ "\n") $ messages return () where readLazy :: IO [String] readLazy = unsafeInterleaveIO $ do { c <- fancyIORead ; liftM2 (++) (return c) readLazy } fancyIORead :: IO [String] fancyIORead = return ["aa","bb"]

Hi Fabian
From the source viewable in the Haddock docs supplied with GHC
- hGetContents calls lazyRead: hGetContents :: Handle -> IO String hGetContents handle = wantReadableHandle "hGetContents" handle $ \handle_ -> do xs <- lazyRead handle return (handle_{ haType=SemiClosedHandle}, xs ) - lazyRead : lazyRead :: Handle -> IO String lazyRead handle = unsafeInterleaveIO $ <<stuff>> hGetContents is using unsafeInterleaveIO already so you don't have to. Best wishes Stephen

On Feb 24, 2010, at 16:17 , Fabian Roth wrote:
Using UnsafeIO, however, leaves a creepy unsafe feeling... I don't fully understand though why it is unsafe. Doesn't hGetContents do the exact same thing (i.e. reading from IO returning a lazy string) but does not require UnsafeIO.
It does; it's just hidden *inside* hGetContents where you can't see it. -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Feb 24, 2010, at 08:53 , Fabian Roth wrote:
main = do messages <- readLazy mapM_ (\x -> putStr $ show x ++ "\n") $ messages
Regardless of anything else going on, that second line will force everything to be read immediately. -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH
participants (4)
-
Ben Millwood
-
Brandon S. Allbery KF8NH
-
Fabian Roth
-
Stephen Tetley