A simple useful case for Template Haskell
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
declareAnimals :: [String] -> Q [Dec]
declareAnimals animals =
return [DataD constraints name vars cons derives]
where
constraints = []
name = mkName "Animal"
vars = []
cons = map (\a -> NormalC (mkName a) fields) animals
fields = []
derives = [''Show, ''Eq, ''Ord, ''Enum, ''Read]
animals = ["Aardvark", "Bobcat", "Quokka"]
$(declareAnimals animals)
-- data Signal = Aardvark | Bobcat | Quokka
If you only cared about getting an enum type, this is where you can jump off.
Digging into the types
If we look further at the types involved here, we can learn more about what this is doing.
At a high level, we’re simply building a haskell AST and introducing it into our code-base. You can think of this as meta-programming but if you ask me it’s more like playing with legos.
We construct a data
declaration with DataD
DataD :: Cxt -> Name -> [TyVarBndr] -> [Con] -> [Name] -> Dec
or if we spread this out and document each piece.
DataD :: Cxt -- constraints (eg. Num a => a -> a)
-> Name -- the type name
-> [TyVarBndr] -- type variable bindings
-> [Con] -- constructors
-> [Name] -- deriving types (eg. Eq, Ord, etc)
-> Dec
simple Name
constructor
mkName :: String -> Name
build a normal Constructor
NormalC :: Name -> [StrictType] -> Con
The derives
need to have the double-tick in order to be read as a syntax builder rather than an actual deriving statement. The same goes for type constraints and such.
You can look at all this documentation on the Language.Haskell.TH page