Search code examples
haskellfile-iolazy-loadinglazy-evaluationlazy-sequences

Create lazy IO list from a non-IO list


I have a lazy list of filenames created by find. I'd like to be able to load the metadata of these files lazily too. That means, that if i take 10 elements from metadata, it should only search the metadata of these ten files. The fact is find perfectly gives you 10 files if you ask for them without hanging your disk, whereas my script searches the metadata of all files.

main = do
    files <- find always always / 
    metadata <- loadMetaList files

loadMetaList :: [String] -> IO [Metadata]
loadMetaList file:files = do
    first <- loadMeta file
    rest <- loadMetaList files
    return (first:rest)

loadMeta :: String -> IO Metadata

As you can see, loadMetaList is not lazy. For it to be lazy, it should use tail recursion. Something like return (first:loadMetaList rest).

How do I make loadMetaList lazy?


Solution

  • The (>>=) of the IO monad is such that in

    loadMetaList :: [String] -> IO [Metadata]
    loadMetaList file:files = do
        first <- loadMeta file
        rest <- loadMetaList files
        return (first:rest)
    

    the action loadMetaList files has to be run before return (first:rest) can be executed.

    You can avoid that by deferring the execution of loadMetaList files,

    import System.IO.Unsafe
    
    loadMetaList :: [String] -> IO [Metadata]
    loadMetaList file:files = do
        first <- loadMeta file
        rest <- unsafeInterleaveIO $ loadMetaList files
        return (first:rest)
    

    with unsafeInterleaveIO (which find also uses). That way, the loadMetaList files is not executed until its result is needed, and if you require only the metadata of 10 files, only that will be loaded.

    It's not quite as unsafe as its cousin unsafePerformIO, but should be handled with care too.