My first thoughts were to use type classes to make the code more generic. Type classes represent a constraint on a type variable in a parametrically polymorphic type. Parametric polymorphism simply means a generic function can be written so it handles values identically independent of type (the list functions are a good example).
So I started, and ended up typing in gibberish like this:
class Report r where
printReport :: r -> String
class (Eq l, Show l) => LogProcessor l where
processLine :: String -> Maybe l
combineUnit :: (Report r) => l -> r -> r
I then read OOP vs type classes and Learning Haskell Notes which made me think that type classes is the wrong approach.
All I'm actually trying to do is change the behaviour of a couple of functions (parsing a line and combining the results). I'm not really interested in any types and there should be as few constraints as possible in terms of what you can implement. The simplest way (and probably the right way) is to just pass these functions in
processFile :: FilePath -> (String -> Maybe t) -> IO([t])
processFile path f = do
a <- readFile path
return (Maybe.mapMaybe f (lines a))
reportFile :: FilePath -> (String -> Maybe t) -> ([t] -> String) -> IO()
reportFile path func comb = do
a <- processFile path func
print (comb a)
return ()
There aren't really any constraints here.
processFile
now processes a file path, returning IO([t])
. A combining function processes this list and generates a string to give the result. Much simpler to use too.