
Paul Moore wrote:
... ie, there's deep dark magic involved in the seemingly simple getContents, which isn't easily available to mere mortals (or even semi-immortal library designers).
That's really not true. getContents looks simple from the outside, and it *can* be simple underneath, too. You can write a getContents on an arbitrary source of data with just one built-in action and two other primitives. The built-in is unsafeInterleaveIO, and the primitives from your data source are "get me the next item" and "am I done yet?". getContents :: MyDataItem a => DataSource a -> IO [a] getContents myDataSource = unsafeInterleaveIO $ do empty <- amIDoneYet myDataSource if empty then return [] else do x <- getMeTheNextItem myDataSource xs <- getContents myDataSource return (x:xs) If you're providing this in a library, you have to consider what you should do for your consumer if amIDoneYet or getMeTheNextItem does "the wrong thing", but that's hardly unusual. You can make the implementation complex, so that it prefetches data, handles exceptions, and whatnot, but the basic idea isn't too terribly scary. You'll see this pattern in a number of Haskell libraries (System.IO, Data.ByteString.Lazy, Control.Concurrent.Chan, and more).