
Hmmm...I'll poke at it. Steve Harris wrote:
[reposted from haskell mailing list where I got no bites :) ]
Folks, I'm new to using monads, and I'd like some help improving a function definition that I wrote and which works, but for which I think there should be a clearer way to write it.
What I'm after is something like: -- (psuedo-code) [(b,c,d) | b <- getDirectoryContents a_dir, c <- getDirectoryContents (a_dir ++ "/" ++ b), d <- getDirectoryContents (a_dir ++ "/" ++ b ++ "/" ++ c) ],
ie. where the generators feed from IO actions instead of lists, but I gather this comprehension style isn't supported, which is too bad because it's really easy to read. (Is this what was meant by "monad comprehensions" that I've heard reference to?)
Note: I will assume getDirectoryContents only returns directories. You could write that only if you computed the directory contents first, perhaps by loading them into nest maps or hashtables or simply lists. The other problem is that you want a 3-tuple. There are few generic ways to create or select items in tuples. Template haskell allows more generic code to construct and destruct tuples. It could define a version paramterised on the integer 3. http://haskell.org/hawiki/TemplateHaskell http://haskell.org/hawiki/TemplateHaskellTutorial
Here's how I actually wrote it, using nested folds:
import System.IO
-- Load directory entries 3 levels deep under a_dir, as list of tuples (b,c,d)
load3DirLevels :: FilePath -> IO [(String,String,String)] load3DirLevels a_dir = do bs <- getDirectoryContents a_dir
foldM (\tups b -> do cs <- getDirectoryContents (a_dir ++ "/" ++ b) foldM (\tups' c -> do ds <- getDirectoryContents (a_dir ++ "/" ++ b ++ "/" ++ c) foldM (\tups'' d -> do return $ (b, c, d) : tups'' ) tups' ds ) tups cs ) [] bs
This function isn't so clear at a glance, and yet what it's doing seems like a pretty common thing to want to do: are there any library functions for monads (or IO in particular) that make this sort of thing easier, or should I to try and write my own function? Looks not too difficult to write but I think I might miss something important if I didn't ask first... How would you do it?
The foldM code, given your pseudo-code as a comment above, is clear. Perhaps this can be made simpler by making it more generic. Somewhat tested: import Control.Monad (liftM) getDirectoryContents _ = return ["a","b","c"] glue :: String -> [String] -> String glue sep values = foldr1 (\a b-> a ++ sep ++ b) values descend :: Int -> [String] -> IO [[String]] descend n _ | n < 0 = fail "Cannot descend negative levels" descend 0 vs = return [vs] descend n vs = do let path = glue "/" vs deeper <- getDirectoryContents path liftM concat (mapM (\v' -> descend (n-1) (vs++[v'])) deeper) listTo3Tuple [top,a,b,c] = (a,b,c) listTo3Tuple _ = error "malformed list" load3DirLevels top = liftM (map listTo3Tuple) (descend 3 [top]) The descend function does the foldM work recursively with a counter, returning a list of lists of path components. Then the tuple is formed. Usually when I use a counter, someone else comes back with a more clever implicit definition, so I'll do that myself: goDeeper :: [[String]] -> IO [[String]] goDeeper [] = return [] goDeeper (vs:vss) = do let path = glue "/" vs deeper <- getDirectoryContents path liftM (([ (vs++[v']) | v'<- deeper ]) ++) (goDeeper vss) load3DirLevels' a_dir = liftM (map listTo3Tuple) (goDeeper [[a_dir]] >>= goDeeper >>= goDeeper) which seems to work