[Haskell-cafe] comprehension generators from IO [a]'s ?

Chris Kuklewicz haskell at list.mightyreason.com
Mon Dec 19 16:07:38 EST 2005


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


More information about the Haskell-Cafe mailing list