Introduction to GHC.Generics
Generic functions traverse the structure of a type to produce the body of a function with the goal of reducing code reptition.
Imagine a simple record type and a function that converts any type to a String
.
data Person
= MkPerson
{ name :: String
, age :: Int
}
genericShow :: a -> String
genericShow = undefined
Here is a simplified version of how we traverse over the structure of the data type to produce the body of the genericShow
function. The following is pseudo code and does not actually compile. It intends to emulate the way GHC.Generics works in a simple, readable manner.
genericShow :: a -> String
genericShow a =
case getTypeRepresentation a of
TypeName name rest = show name ++ genericShow rest
Constructor constructor rest = show constructor ++ genericShow rest
NamedField (Just fieldName) typ = show fieldName ++ show type
UnnamedField Nothing type = show typ
ConcatFields field1 field2 = genericShow field1 ++ genericShow field2
ConcatConstructors constructor1 constructor2 = genericShow constructor1 ++ genericShow constructor2
TypeParameter a = genericShow a
NoFields = ""
Examples of Generic Representations
Product (Record with Fields)
data Person
= MkPerson
{ name :: String
, age :: Int
}
TypeName "Person"
(Constructor "MkPerson"
(NamedField (Just "name") "String"
`ConcatFields`
NamedField (Just "age") "Int"))
Sum Type without Values
Sum Type with One Value
Sum Type with Multiple Constructors and Type Parameters
data Either a b
= Left a
| Right b
TypeName "Either"
((Constructor "Left" (TypeParameter a))
`ConcatConstructors`
(Constructor "Right" (TypeParameter b)))
GHC.Generic Types
We can view use our simple representation as type synonyms for the real types in GHC.Generics.
type TypeName = D1 ('MetaSelector name moduleName packageName isNewType) rest
type Constructor = C1 ('MetaCons constructor prefixOrInfix isRecord) rest
type NamedField = S1 ('MetaSel ('Just selector) unpackednesss sourceStrictness decidedStrictness)
type UnnamedField = S1 ('MetaSel Nothing unpackednesss sourceStrictness decidedStrictness)
type ConcatFields = (:*:)
type ConcatConstructors = (:+:)
type TypeParameter a = Rec0 a
type NoFields = U1
GHC.Generic Representations
Now we will take a look at the real generic repsentations of the types from above. In GHCi, you can see the representations by calling :kind! Rep
on any type that derives Generic
Product (Record with Fields)
:set -XDeriveGeneric
import GHC.Generics (Generic, Rep)
data Person
= MkPerson
{ name :: String
, age :: Int
} deriving Generic
:kind! Rep Person
Rep Person :: * -> *
= D1
('MetaData "Person" "Ghci2" "interactive" 'False)
(C1
('MetaCons "MkPerson" 'PrefixI 'True)
(S1
('MetaSel
('Just "name")
'NoSourceUnpackedness
'NoSourceStrictness
'DecidedLazy)
(Rec0 String)
:*: S1
('MetaSel
('Just "age")
'NoSourceUnpackedness
'NoSourceStrictness
'DecidedLazy)
(Rec0 Int)))
Sum Type without Values
Sum Type with One Value
data Wrapper = Wrapper Int deriving Generic
Rep Wrapper :: * -> *
= D1
('MetaData "Wrapper" "Ghci4" "interactive" 'False)
(C1
('MetaCons "Wrapper" 'PrefixI 'False)
(S1
('MetaSel
'Nothing
'NoSourceUnpackedness
'NoSourceStrictness
'DecidedLazy)
(Rec0 Int)))
Sum Type with Multiple Constructors and Type Parameters
data Either a b
= Left a
| Right b
deriving Generic
:kind! Rep (Either Int String)
Rep (Either Int String) :: * -> *
= D1
('MetaData "Either" "Ghci5" "interactive" 'False)
(C1
('MetaCons "Left" 'PrefixI 'False)
(S1
('MetaSel
'Nothing
'NoSourceUnpackedness
'NoSourceStrictness
'DecidedLazy)
(Rec0 Int))
:+: C1
('MetaCons "Right" 'PrefixI 'False)
(S1
('MetaSel
'Nothing
'NoSourceUnpackedness
'NoSourceStrictness
'DecidedLazy)
(Rec0 [Char])))
You can also see the generic representations when you build the package: stack build --ghc-options=-ddump-deriv
.