Data.Generics (aka SYB)

The documentation of Data.Generics, aka syb or scrap your boilerplate, is extremely ... shall we say ... terse. So when I figure something out I like to make a note about it here so I can refer back. Anything you want to use the Data.Generics library on must be an instance of Data (Typeable?). So what can you do?

Making Queries

The mkQ function turns a normal function of type a into a function suitable for use where a GenericQ is expected. For example,
% ghci
λ :set -XDeriveDataTypeable
λ :m +Data.Generics
λ data T = T Int Char Double deriving (Data, Show)
λ mkQ "?" (show :: T -> String) (T 1 'c' 3.5)
"T 1 'c' 3.5"
(It is important to understand how we are applying a signature to the function show to restrict it to operate only on type T. Without this the type of show is show :: Show a => a -> String, which will not be recognized by the syb mechansims as matching T.) The function gmapQ operates on the "immediate subterms" of the value, hence
λ gmapQ (mkQ "?" (show :: Int -> String)) (T 1 'c' 3.5)
["1","?","?"]
We can use extQ to "extend" our query function to operate on more than one type:
λ gmapQ (mkQ "?" (show :: Int -> String) `extQ` (show :: Char -> String)) (T 1 'c' 3.5)
["1","'c'","?"]
We use mkQ to create queries - functions that find values of a set of particular types and converts each to a single type. Similarly, we can use mkT to create transformations, which convert values of the generic types to different values of the generic type:
λ everywhere (mkT ((+ 1) :: Int -> Int)) (T 1 'c' 3.5)
(T 2 'c' 3.5)
λ everywhere (mkT ((+ 1) :: Int -> Int) `extT` (const 'z' :: Char -> Char)) (T 1 'c' 3.5)
(T 2 'z' 3.5)

And now...

I have a large data structure for which I currently use template haskell to generate the function peekRow :: s -> [Peek s]. The elements of this list normally correspond to the fields of whatever the data structure s represents, but there is a list of exceptions to this rule for types Either, Maybe, (a, b), [a], Map, and a type similar to Map named Order. What does this look like if I switch from template haskell to syb? Here is an example of the code generated for a structure named ImageSize with three fields:
peekRow (Proxy) (_s@(ImageSize {})) =
    mconcat [concatMap (\pth -> case pth of
                                  p@(Path_ImageSize_dim _wp) -> map (\a -> peekCons p (Just a)) (toListOf (toLens p) _s :: [Dimension])
                                  _ -> [])
                       (paths (Proxy :: Proxy Univ) _s (Proxy :: Proxy Dimension)),
             concatMap (\pth -> case pth of
                                  p@(Path_ImageSize_size _wp) -> map (\a -> peekCons p (Just a)) (toListOf (toLens p) _s :: [Double])
                                  _ -> [])
                       (paths (Proxy :: Proxy Univ) _s (Proxy :: Proxy Double)),
             concatMap (\pth -> case pth of
                                  p@(Path_ImageSize_units _wp) -> map (\a -> peekCons p (Just a)) (toListOf (toLens p) _s :: [Units])
                                  _ -> [])
                       (paths (Proxy :: Proxy Univ) _s (Proxy :: Proxy Units))] :: [Peek Univ ImageSize]
The paths function returns a list; this needs to be changed to a fold.