Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 700
Andras Gyomrey Avatar asked Dec 03 '22 23:12

Andras Gyomrey


1 Answers

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.

like image 132
Daniel Fischer Avatar answered Dec 28 '22 14:12

Daniel Fischer