haskell - Filter a list of paths to only include files -


if have list of filepaths, how can filter them return ones regular files (namely, not symlinks or directories)?

for example, using getdirectorycontents

main =     contents <- getdirectorycontents "/foo/bar"     let onlyfiles = filterfunction contents in         print onlyfiles 

where "filterfunction" function returns filepaths represent files.

the answer may work on linux, cross platform support preferred.

[edit] using doesdirectoryexist doesn't work expected. script prints list of in directory, not files:

module main  import system.directory import control.monad (filterm, liftm)  getfiles :: filepath -> io [filepath] getfiles root =     contents <- getdirectorycontents root     fileshere <- filterm (liftm not . doesdirectoryexist) contents     subdirs <- filterm doesdirectoryexist contents     return fileshere  main =     files <- getfiles "/"     print $ files 

additionally, variable subdirs contain "." , "..".

to find standard library functions, hoogle great resource; it's haskell search engine lets search by type. using requires figuring out how think types haskell way™, though, proposed type signatures doesn't quite work with. so:

  1. you're looking [filepath] -> [filepath]. remember, haskell spelling filepath. so…

  2. you're looking [filepath] -> [filepath]. unnecessary; if want filter things, should use filter. so…

  3. you're looking function of type filepath -> bool can pass filter. can't quite right: function needs query file system, effect, , haskell tracks effects in type system using io. so…

  4. you're looking function of type filepath -> io bool.

and if search on hoogle, first result doesfileexist :: filepath -> io bool system.directory. docs:

the operation doesfileexist returns true if argument file exists , not directory, , false otherwise.

so system.directory.doesfileexist want. (well… little work! see below.)

now, how use it? can't use filter here, because have effectful function. use hoogle again – if filter has type (a -> bool) -> [a] -> [a], annotating results of functions monad m gives new type monad m => (a -> m bool) -> [a] -> m [bool] – there's easier "cheap trick". in general, if func function effectful/monadic version, effectful/monadic version called funcm, , lives in control.monad.¹ , indeed, there function control.monad.filterm :: monad m => (a -> m bool) -> [a] -> m [a].

however! hate admit it, in haskell, types don't provide information need. importantly, we're going have problem here:

  • file paths given arguments functions interpreted relative current directory, but…
  • getdirectorycontents returns paths relative argument.

thus, there 2 approaches can take fix things. first adjust results of getdirectorycontents can interpreted correctly. (we discarding . , .. results, although if you're looking regular files won't hurt anything.) return file names include directory contents being examined. adjust getdirectorycontents function looks this:

getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp =     map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp 

the filter gets rid of special directories, , map prepends argument directory results. makes returned files acceptable arguments doesfileexist. (if haven't seen them before, (system.filepath.</>) appends 2 file paths; , (control.applicative.<$>), available (data.functor.<$>), infix synonym fmap, liftm more broadly applicable.)

putting together, final code becomes:

import control.applicative import control.monad import system.filepath import system.directory  getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp =     map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp  main :: io () main =   contents  <- getqualifieddirectorycontents "/foo/bar"   onlyfiles <- filterm doesfileexist contents   print onlyfiles 

or, if feel being fancy/point-free:

import control.applicative import control.monad import system.filepath import system.directory  getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp =     map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp  main :: io () main =   print      =<< filterm doesfileexist      =<< getqualifieddirectorycontents "/foo/bar" 

the second approach adjust things doesfileexist runs appropriate current directory. return file name relative directory contents being examined. this, want use withcurrentdirectory :: filepath -> io -> io a function (but see below), , pass getdirectorycontents current directory "." argument. documentation withcurrentdirectory says (in part):

run io action given working directory , restore original working directory afterwards, if given action fails due exception.

putting gives following code

import control.monad import system.directory  main :: io () main = withcurrentdirectory "/foo/bar" $          print =<< filterm doesfileexist =<< getdirectorycontents "." 

this want, unfortunately, it's available in version 1.3.2.0 of directory package – of writing, recent one, , not 1 have. luckily, it's easy function implement; such set-a-value-locally functions implemented in terms of control.exception.bracket :: io -> (a -> io b) -> (a -> io c) -> io c. bracket function run bracket before after action, , correctly handles exceptions. can define withcurrentdirectory ourselves:

withcurrentdirectory :: filepath -> io -> io withcurrentdirectory fp m =   bracket getcurrentdirectory setcurrentdirectory $ \_ ->     setcurrentdirectory fp     m 

and use final code:

import control.exception import control.monad import system.directory  withcurrentdirectory :: filepath -> io -> io withcurrentdirectory fp m =   bracket getcurrentdirectory setcurrentdirectory $ \_ ->     setcurrentdirectory fp     m  main :: io () main = withcurrentdirectory "/foo/bar" $          print =<< filterm doesfileexist =<< getdirectorycontents "." 

also, 1 quick note lets in dos: in do block,

do ...foo...    let x = ...bar...    ...baz... 

is equivalent to

do ...foo...    let x = ...bar... in      ...baz... 

so example code doesn't need in in let , can outdent print call.


¹ not always: want different classes of effects! use applicative control.applicative when possible; more things applicatives monads (although means can less them). in case, effectful functions may live there, or in data.foldable or data.traversable.


Comments