Do not use unsafeInterleaveIO
or any lazy IO for that matter. This is precisely the problem that iteratees were created to address: avoiding lazy IO, which gives unpredictable resource management. The trick is to never build the list and constantly stream it using iteratees until you are done using it. I will use examples from my own library, pipes
, to demonstrate this.
First, define:
import Control.Monad
import Control.Monad.Trans
import Control.Pipe
-- Demand only 'n' elements
take' :: (Monad m) => Int -> Pipe a a m ()
take' n = replicateM_ n $ do
a <- await
yield a
-- Print all incoming elements
printer :: (Show a) => Consumer a IO r
printer = forever $ do
a <- await
lift $ print a
Now let's be mean to our user and demand they produce the really large list for us:
prompt100 :: Producer Int IO ()
prompt100 = replicateM_ 1000 $ do
lift $ putStrLn "Enter an integer: "
n <- lift readLn
yield n
Now, let's run it:
>>> runPipe $ printer <+< take' 1 <+< prompt100
Enter an integer:
3<Enter>
3
It only prompts the user for one integer, since we only demand one integer!
If you want to replace prompt100
with output from getLargeList
, you just write:
yourProducer :: Producer b IO ()
yourProducer = do
xs <- lift getLargeList
mapM_ yield xs
... and then run:
>>> runPipe $ printer <+< take' 1 <+< yourProducer
This will lazily stream the list and never build the list in memory, all without using unsafe IO
hacks. To change how many elements you demand, just change the value you pass to take'
For more examples like this, read the pipes
tutorial at Control.Pipe.Tutorial
.
To learn more about why lazy IO causes problems, read Oleg's original slides on the subject, which you can find here. He does a great job of explaining the problems with using lazy IO. Any time you feel compelled to use lazy IO, what you really want is an iteratee library.