[Template-haskell] example of derive using Template Haskell?

Sean Seefried sseefried at cse.unsw.edu.au
Tue Jan 11 20:23:32 EST 2005


> I have {-# OPTIONS -fglasgow-exts #-} at the top of both modules.  
> Does that not imply -fth?
>
> I do realize that my example is not valid Haskell code.  I am looking 
> for an example of valid Haskell code that allows me to derive a class.
>
> Or even just an example of actualy usage of [d|...] that works with 
> GHC 6.2.2!
>
> SO far I haven't been able to find one.
>

The following module derives Data and Typeable instances. I hope it's 
of some use to you.

Sean

--------------

{-# OPTIONS -fth #-}
--
-- The bulk of this module was shamelessly ripped from Ulf Norell,
-- winner of the  Succ-zeroth International Obfuscated Haskell Code 
Contest. I
-- mention this as it was from his winning entry that this module began.
--
--
-- I have extended it to deal with data definitions with type variables.
-- I also added the coments.
--
-- Sean Seefried 2004
--

module DeriveData.DeriveData where

import Language.Haskell.TH
import Data.List
import Data.Char
import Data.Generics
import Control.Monad

-- maximum type paramters for a Typeable instance
maxTypeParams = 7


--
-- | Takes the name of an algebraic data type, the number of type 
parameters
--   it has and creates a Typeable instance for it.
deriveTypeable :: Name -> Int -> Q [Dec]
deriveTypeable name nParam
  | nParam <= maxTypeParams =
     sequence
      [ instanceD (return [])
         (conT typeableName `appT` conT name)
	[ funD typeOfName [clause [wildP] (normalB
	   [| mkTyConApp (mkTyCon $(litE $ stringL (nameBase name))) [] |]) []]
	]
      ]
  | otherwise = error ("Typeable classes can only have a maximum of " ++
		      show maxTypeParams ++ " parameters")
  where
    typeableName
      | nParam == 0 = mkName "Typeable"
      | otherwise   = mkName ("Typeable" ++ show nParam)
    typeOfName
      | nParam == 0  = mkName "typeOf"
      | otherwise    = mkName ("typeOf" ++ show nParam)

--
-- | Takes a name of a algebraic data type, the number of parameters it
--   has and a list of constructor pairs.  Each one of these constructor
--   pairs consists of a constructor name and the number of type
--   parameters it has.  The function returns an automatically generated
--   instance declaration for the Data class.
--
--   Doesn't do gunfold, dataCast1 or dataCast2
deriveData :: Name -> Int -> [(Name, Int)] -> Q [Dec]
deriveData name nParam cons =
     sequence (
      conDecs ++

      [ dataTypeDec
      , instanceD context
           (conT ''Data `appT` (foldl1 appT ([conT name] ++ 
typeQParams)))
	[ funD 'gfoldl
	    [ clause (map (varP . mkName) ["f", "z", "x"])
		(normalB $ caseE (varE (mkName "x")) (map mkMatch cons))
		[]
	    ]
	, funD 'gunfold
	  [clause [wildP, wildP, wildP ]
	   (normalB [| error "gunfoldl not defined" |]) []]
	, funD 'toConstr
	    [ clause [varP (mkName "x")]
		(normalB $ caseE (varE (mkName "x"))
		 (zipWith mkSel cons conVarExps))
		[]
	    ]
	, funD 'dataTypeOf
	    [ clause [wildP] (normalB $ varE (mkName dataTypeName)) []
	    ]
	]
      ])
     where
         paramNames = take nParam (zipWith (++) (repeat "a") (map show 
[0..]))
	typeQParams = map (\nm -> varT (mkName nm)) paramNames
         context = cxt (map (\typ -> conT ''Data `appT` typ) typeQParams)

         -- Takes a pair (constructor name, number of type arguments) and
         -- creates the correct definition for gfoldl
         -- It is of the form z <constr name> `f` arg1 `f` ... `f` argn
	mkMatch (c,n) =
	    do	vs <- mapM (\s -> newName s) names
		match	(conP c $ map varP vs)
			(normalB $ foldl
			   (\e x -> (varE (mkName "f") `appE` e) `appE` varE x)
				    (varE (mkName "z") `appE` conE c)
				    vs
			) []
           where names = take n (zipWith (++) (repeat "x") (map show 
[0..]))
         lowCaseName = map toLower nameStr
         nameStr = nameBase name
         dataTypeName = lowCaseName ++ "DataType"
         -- Creates dataTypeDec of the form:
         -- <name>DataType = mkDataType <name> [<constructors]
         dataTypeDec = funD (mkName dataTypeName)
		       [clause []
			(normalB
			 [| mkDataType nameStr $(listE (conVarExps)) |]) [] ]
		
         -- conVarExps is a [ExpQ]. Each ExpQ is a variable expression
         -- of form varE (mkName <con>Constr)
         numCons = length cons
         constrNames =
           take numCons (map (\i -> dataTypeName ++ show i ++ "Constr") 
[1..])
         conNames = map (nameBase . fst) cons
         conVarExps = map (varE . mkName) constrNames
	conDecs = zipWith mkConstrDec constrNames conNames
          where
           mkConstrDec decNm conNm =
             funD (mkName decNm)
		     [clause []
			(normalB
			 [| mkConstr $(varE (mkName dataTypeName)) conNm []
		            $(fixity conNm)
			 |]) []]
		
	fixity (':':_)	= [| Infix |]
	fixity _	= [| Prefix |]

	mkSel (c,n) e = match  (conP c $ replicate n wildP)
			 (normalB e) []

deriveMinimalData :: Name -> Int  -> Q [Dec]
deriveMinimalData name nParam  = do
   decs <- qOfDecs
   let listOfDecQ = map return decs
   sequence
     [ instanceD context
         (conT ''Data `appT` (foldl1 appT ([conT name] ++ typeQParams)))
         listOfDecQ ]

   where
     paramNames = take nParam (zipWith (++) (repeat "a") (map show 
[0..]))
     typeQParams = map (\nm -> varT (mkName nm)) paramNames
     context = cxt (map (\typ -> conT ''Data `appT` typ) typeQParams)
     qOfDecs =
       [d| gunfold _ _ _ = error ("gunfold not defined")
           toConstr x    = error ("toConstr not defined for " ++
                              show (typeOf x))
           dataTypeOf x = error ("dataTypeOf not implemented for " ++
                            show (typeOf x))
           gfoldl f z x = z x
        |]

{- instance Data NameSet where
   gunfold _ _ _ = error ("gunfold not implemented")
   toConstr x = error ("toConstr not implemented for " ++ show (typeOf 
x))
   dataTypeOf x = error ("dataTypeOf not implemented for " ++ show 
(typeOf x))
   gfoldl f z x = z x -}

typeInfo :: DecQ -> Q (Name, Int, [(Name, Int)])
typeInfo m =
     do	d <- m
         case d of
           d@(DataD _ _ _ _ _) ->
	    return $ (simpleName $ name d, paramsA d, consA d)
           d@(NewtypeD _ _ _ _ _) ->
	    return $ (simpleName $ name d, paramsA d, consA d)
           _ -> error ("derive: not a data type declaration: " ++ show d)

     where
	consA (DataD _ _ _ cs _)    = map conA cs
	consA (NewtypeD _ _ _ c _)  = [ conA c ]

         paramsA (DataD _ _ ps _ _) = length ps
         paramsA (NewtypeD _ _ ps _ _) = length ps


	conA (NormalC c xs)	    = (simpleName c, length xs)
	conA (RecC c xs)	    = (simpleName c, length xs)
	conA (InfixC _ c _)	    = (simpleName c, 2)

	name (DataD _ n _ _ _)	    = n
	name (NewtypeD _ n _ _ _)   = n
	name d			    = error $ show d

simpleName :: Name -> Name
simpleName nm =
   let s = nameBase nm
   in case dropWhile (/=':') s of
	[]	    -> mkName s
	_:[]        -> mkName s
	_:t	    -> mkName t

--
-- | Derives the Data and Typeable instances for a single given data 
type.
--
deriveOne :: Name -> Q [Dec]
deriveOne name =
  do	info' <- reify name
         case info' of
           TyConI d -> do
             (name, nParam, ca) <- typeInfo ((return d) :: Q Dec)
	    t <- deriveTypeable name nParam
	    d <- deriveData name nParam ca
	    return $ t ++ d
           _ -> error ("derive: can't be used on anything but a type " ++
		      "constructor of an algebraic data type")

--
-- | Derives Data and Typeable instances for a list of data
--   types. Order is irrelevant. This should be used in favour of
--   deriveOne since Data and Typeable instances can often depend on
--   other Data and Typeable instances - e.g. if you are deriving a
--   large, mutually recursive data type.  If you splice the derived
--   instances in one by one you will need to do it in depedency order
--   which is difficult in most cases and impossible in the mutually
--   recursive case. It is better to bring all the instances into
--   scope at once.
--
--  e.g. if
--     data Foo = Foo Int
--  is declared in an imported module then
--     $(derive [''Foo])
--  will derive the instances for it
derive :: [Name] -> Q [Dec]
derive names = do
  decss <- mapM deriveOne names
  return (concat decss)


--
-- | This function is much like deriveOne except that it brings into
--   scope an instance of Data with minimal definitions. gfoldl will
--   essentially leave a data structure untouched while gunfoldl,
--   toConstr and dataTypeOf will yield errors.
--
--   This function is useful when you are certain that you will never
--   wish to transform a particular data type.  For instance you may
--   be transforming another data type that contains other data types,
--   some of which you wish to transform (perhaps recursively) and
--   some which you just wish to return unchanged.
--
--   Sometimes you will be forced to use deriveMinimalOne because you 
--   do not have access to the contructors of the data type (perhaps
--   because it is an Abstract Data Type). However, should the
--   interface to the ADT be sufficiently rich it is possible to
--   define you're own Data and Typeable instances.
deriveMinimalOne :: Name -> Q [Dec]
deriveMinimalOne name =
  do	info' <- reify name
         case info' of
           TyConI d -> do
             (name, nParam, _) <- typeInfo ((return d) :: Q Dec)
	    t <- deriveTypeable name nParam
	    d <- deriveMinimalData name nParam
	    return $ t ++ d
           _ -> error ("deriveMinimal: can't be used on anything but a 
type " ++
		      "constructor of an algebraic data type")


deriveMinimal :: [Name] -> Q [Dec]
deriveMinimal names = do
   decss <- mapM deriveMinimalOne names
   return (concat decss)



More information about the template-haskell mailing list