jmtd → log → Generic Haskell
When I did the work described earlier in template haskell, I also explored generic programming in Haskell to solve a particular problem. StrIoT is a program generator: it outputs source code, which may depend upon other modules, which need to be imported via declarations at the top of the source code files.
The data structure that StrIoT manipulates contains information about what
modules are loaded to resolve the names that have been used in the input code,
so we can walk that structure to automatically derive an import list. The
generic programming tools I used for this are from
Scrap Your Boilerplate
(SYB), a module written to complement
a paper of the same name.
In this code snippet,
mkQ are from SYB:
extractNames :: Data a => a -> [Name] extractNames = everything (++) (\a -> mkQ  f a) where f = (:)
The input must be any type which implements typeclass
Data, as must all its
members (and their members etc.): This holds for the Template Haskell
types. The output is a normal list of
Names. The utility function has a
more specific type
Name -> [Name]. This is all that's needed to walk over
the heterogeneous data structures and do something specific (
f) when we
Names to get a list of modules is simple
nub . catMaybes . map nameModule . concatMap extractNames
Unfortunately, there's a weird GHC behaviour relating to the module names
for some Prelude functions that makes the above less useful in practice.
For example, the Prelude function
words :: String -> [String] can normally
be used without an explicit import (since it's a Prelude function). However,
once round-tripped through a
Name, it becomes
GHC.OldList fails in some contexts, because it's a hidden module or
package. I've been meaning to investigate further and, if necessary, file a GHC
bug about this.
For this reason I've scrapped all the above and gone with a different plan. We
go back to requiring the user to specify their required import list explicitly.
We then walk over the
Exp data type prior to code generation and decanonicalize
Names. I also use generic programming/SYB to do this:
unQualifyNames :: Exp -> Exp unQualifyNames = everywhere (\a -> mkT f a) where f :: Name -> Name f n = if n == '(.) then mkName "." else (mkName . last . splitOn "." . pprint) n
I've had to special-case composition (
.) since that code-point is also used
as the delimiter between package, module and function. Otherwise this looks
very similar to the earlier function, except using
transformation) instead of
mkQ (make query).