I am trying to do a recursive descent of a directory structure using Haskell. I would like to only retrieve the child directories and files as needed (lazily).
I wrote the following code, but when I run it, the trace shows that all directories are visited before the first file:
module Main where
import Control.Monad ( forM, forM_, liftM )
import Debug.Trace ( trace )
import System.Directory ( doesDirectoryExist, getDirectoryContents )
import System.Environment ( getArgs )
import System.FilePath ( (</>) )
-- From Real World Haskell, p. 214
getRecursiveContents :: FilePath -> IO [FilePath]
getRecursiveContents topPath = do
names <- getDirectoryContents topPath
let
properNames =
filter (`notElem` [".", ".."]) $
trace ("Processing " ++ topPath) names
paths <- forM properNames $ \name -> do
let path = topPath </> name
isDirectory <- doesDirectoryExist path
if isDirectory
then getRecursiveContents path
else return [path]
return (concat paths)
main :: IO ()
main = do
[path] <- getArgs
files <- getRecursiveContents path
forM_ files $ \file -> putStrLn $ "Found file " ++ file
How can I interleave the file processing with the descent? Is the problem that the files <- getRecursiveContents path
action gets performed before the following forM_
in main
?
This is exactly the kind of problem that iteratees/coroutines were designed to solve.
You can easily do this with
pipes
. The only change I made to yourgetRecursiveContents
was to make it aProducer
ofFilePath
s and torespond
with the file name instead of returning it. This lets downstream handle the file name immediately instead of waiting forgetRecursiveContents
complete.This prints out each file immediately as it traverses the tree, and it does not require lazy
IO
. It's also very easy to change what you do with the file names, since all you have to do is switch out theuseD
stage with your actual file handling logic.To learn more about
pipes
, I highly recommend you read Control.Proxy.Tutorial.I was recently looking at a very similar problem, where I'm trying to do a somewhat complicated search using the
IO
monad, stopping after I find the file I'm interested in. While the solutions using libraries like Enumerator, Conduit, etc. seem to be the best you could do at the time those answers were posted, I just learnedIO
became an instance ofAlternative
in GHC's base library about a year ago, which opens up some new possibilities. Here's the code I wrote to try it out:The
searchFiles
function does a depth-first search of a directory tree, stopping when it finds what you're looking for, as determined by the function passed as the first argument. ThematchFile
function is just there to show how to construct a suitable function to use as the first argument forsearchFiles
; in real life you'd probably do something more complicated.The interesting thing here is that now you can use
empty
to make anIO
computation "give up" without returning a result, and you can chain computations together withasum
(which is justfoldr (<|>) empty
) to keep trying computations until one of them succeeds.I find it a little unnerving that the type signature of an
IO
action no longer reflects the fact that it may deliberately not produce a result, but it sure simplifies the code. I was previously trying to use types likeIO (Maybe a)
, but doing so made it very hard to compose actions.IMHO there's no longer much reason to use a type like
IO (Maybe a)
, but if you need to interface with code that uses a type like that, it's easy to convert between the two types. To convertIO a
toIO (Maybe a)
, you can just useControl.Applicative.optional
, and going the other way, you can use something like this:Using lazy IO /
unsafe...
is not a good way to go. Lazy IO causes many problems, including unclosed resources and executing impure actions within pure code. (See also The problem with lazy I/O on Haskell Wiki.)A safe way is to use some iteratee/enumerator library. (Replacing problematic lazy IO was the motivation for developing these concepts.) Your
getRecursiveContents
would become a source of data (AKA enumerator). And the data will be consumed by some iterator. (See also Enumerator and iteratee on Haskell wiki.)There is a tutorial on the enumerator library that just gives an example of traversing and filtering directory tree, implementing a simple find utility. It implements method
which is basically just what you need. I believe you will find it interesting.
Also there is a nice article explaining iteratees in The Monad Reader, Issue 16: Iteratee: Teaching an Old Fold New Tricks by John W. Lato, the author of the iteratee library.
Today many people prefer newer libraries such as pipes. You may be interested in a comparison: What are the pros and cons of Enumerators vs. Conduits vs. Pipes?.
Thanks to the comment by Niklas B., here is the solution that I have:
Is there a better way?