apfelmus' interface degustation - System.FilePath.Find

apfelmus apfelmus at quantentunnel.de
Thu Jun 28 13:43:10 EDT 2007

Chers amis of functionaλ cooking,

aujourd'hui, le maître de cuisine is going to criticize


Is it seasoned too much with Curry and Monads? Pure or diluée with side
effects? Apfelmus will tell you! And like every serious cooking critic,
le maître thinks no end of himself so he can judge the haddock without
eating the compiled meal.

Who did not yet have to scan the file buffet for data of his gusto? This
is what System.FilePath.Find helps you to do. It basically offers a function

 find :: RecursionPredicate
    -> FilterPredicate
    -> FilePath
    -> IO [FilePath]

to lazily traverse a directory tree filtering out files that do not
match the FilterPredicate. The RecursionPredicate decides whether to
recurse into or a sub-directory or not (it could be mentioned explicitly
that the predicate is only invoked on directories). A variant of  find
includes user-defined error handling.

How to define predicates? Mon dieu! They are to be obtained from a monad

  FindClause a
  type FilterPredicate = FindClause Bool

which offers operations like

  extension :: FindClause FilePath
  (==?)     :: Eq a => FindClause a -> a -> FindClause Bool
  (||?) = liftM2 (||)

One example predicate would be

  (extension ==? ".hs") ||? (extension ==? ".lhs")

Cette soupe is much too monadic! In particular, we have the isomorphism

  (FileInfo -> a) ≅ FindClause a

witnessed by the function pair

  (f,g) = ((`liftM` fileInfo), evalClause)

Of course, the encapsulation was made to allow a formulation like

  extension ==? ".hs"

which does not mention the FileInfo parameter. But alas, this can also
be achieved more naturally by appealing to the

  instance Control.Applicative ((->) a)

Abstracting the concrete representation away into FindClause hinders
reuse of already existing functionality from System.FilePath, although
the name "extension" is arguably more succinct than "takeExtension".

The monad could make sense if the predicate might involve not only the
file status but also looking inside the file. Returning all files with a
certain magic number would be such a use case but System.FilePath.Find
currently does not offer zis possibilité.

Un autre point is that the library offers a function  fold  that is
almost a foldl' over all files in a directory tree, but not quite: here,
the fold goes over FileInfos but the list is a simple list of FilePaths.
One has to foldM over the latter and get the file infos again to achieve
the same effect.

Le maître de cuisine thinks that these quirks stem from the fact that
the file system lacks a purely functional design. The observation is
that given persistence, reading does not need to be monadic. In other
words, directory tree traversal might well return a list of files

  data File = File { contents :: String, status :: FileStatus }

with contents and status. More generally, the traversal could be a
side-effect-free traversal of a pure data structure.
Of course, a purely functional file system needs further research, also
due to performance requirements.

Another direction of généralisation is that XML data is in principle
similar to a file system. Shouldn't both have a common traversal interface?

Last but not least, every modern cooking critic includes a

   Rating: λλλ

Le maître awards three λ since the library uses a monad where none
should be, but acknowledges that this was for the ability to formulate
queries without plumbing the FileInfo-parameter everywhere.

Bon appetit,

More information about the Libraries mailing list