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.