Search code examples
listfilehaskellpartitionio-monad

Haskell: how to make a list of files and a list of directories out of one common list


This is a newbie question. Suppose I want to separate a list of files and directories into a list of files and a list of directories:

getFilesAndDirs :: [FilePath] -> ([FilePath], [FilePath])
getFilesAndDirs paths =
  let ...
  in (dirs, files)

Probably this is a hopeless duplicate, I just miss the right keywords. What is the right way to do (and call) this?

The files and the directories occur randomly.


Solution

  • The Data.List package has the partition :: (a -> Bool) -> [a] -> ([a],[a]) function which splits a list of as into a tuple of two lists of as based on a predicate.

    The problem is however that when we check if a file is a directory, we probably will use isDirectory :: FilePath -> IO Bool so we can not directly use this as a predicate (since IO Bool is not equal to Bool).

    We can write our own partitionM however, and use that one:

    import Data.Bool(bool)
    import Data.Foldable(foldrM)
    
    partitionM :: (Foldable t, Monad m) => (a -> m Bool) -> t a -> m ([a], [a])
    partitionM p = foldrM (selectM p) ([],[])
    
    selectM :: Monad m => (a -> m Bool) -> a -> ([a], [a]) -> m ([a], [a])
    selectM p x (ts,fs) = p x >>= return . bool (ts, x:fs) (x:ts,fs)
    

    we can then use it like:

    import System.Directory(isDirectory)
    
    getFilesAndDirs :: [FilePath] -> IO ([FilePath], [FilePath])
    getFilesAndDirs = partitionM isDirectory

    Note that it is an IO ([FilePath], [FilePath]), since we need to perform I/O to check if a path is indeed a directory (and not a file).