Haskell unsafeInterleaveIO and Lazy list constructed from IO actions

Haskell is a lazy language, which means you can easily define an infinite list with recursion:

 
lazySeq = (1:lazySeq)
print $ take 8 lazySeq
 

But it won't works for IO. Because IO is strict by definition. To illustrate this, look at the example below.

 
 
import qualified Data.ByteString        as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Char8 as L8
import Data.ByteString.Lazy.Internal
import System.IO (Handle,openBinaryFile,stdin,stdout,withBinaryFile,IOMode(..),hClose)
import System.IO.Unsafe
 
L8.writeFile "c:\\a.txt" $ L8.pack "chunk1,chunk2,chunk3,chunk4,chunk5,"
 
:{
hGetContentsNStrict::Int -> Handle -> IO ByteString
hGetContentsNStrict k h = lazyRead
  where
    lazyRead = do
      c <- S.hGetSome h k
      if S.null c
        then putStr "end\n" >> hClose h >> return Empty
      else do
           cs <- lazyRead
           L8.putStr c >> putStr "\n" >> return (Chunk c cs)
:}
 
bs = openBinaryFile "C:\\a.txt" ReadMode >>= hGetContentsNStrict 7
:{
do
  cs <- bs
  return $ L.take 1 cs
:}
 
 

The function is modified based on Data.ByteString.Lazy.hGetContentsN , with the unsafeInterleaveIO invocation being removed.

 
end
chunk5,
chunk4,
chunk3,
chunk2,
chunk1,
"c"
 

The function will keep reading a chunk, save it to current level of closure and then get into a deeper level of recursion until it reaches the end of file, that's why the putStr output is reversed.

 
:{
hGetContentsNLazy::Int -> Handle -> IO ByteString
hGetContentsNLazy k h = lazyRead
  where
    lazyRead = unsafeInterleaveIO $ do
      c <- S.hGetSome h k
      if S.null c
        then putStr "end\n" >> hClose h >> return Empty
      else do
           cs <- lazyRead
           L8.putStr c >> putStr "\n" >> return (Chunk c cs)
:}
 

To make it lazy, simply add the unsafeInterleaveIO in front of the recursion body. Here is the output

 
> :{
| do
|   cs <- bs
|   return $ L.take 1 cs
| :}
"chunk1,
c"
 
> :{
| do
|   cs <- bs
|   return $ L.take 14 cs
| :}
"chunk1,
chunk1,chunk2,
chunk2,"
 

The IO and the chunk list is constructed on demand.