module Language.Haskell.TH.Fold (fold) where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Control.Monad
replaceAt :: Integral a => a -> b -> [b] -> [b]
replaceAt _ _ [] = []
replaceAt 0 y (_:xs) = y : xs
replaceAt (n+1) y (x:xs) = x : replaceAt n y xs
arity :: (Num a) => Type -> a
arity (ForallT _ _ t) = arity t
arity (AppT (AppT ArrowT _) t) = 1 + arity t
arity _ = 0
conName :: Con -> Name
conName (NormalC name _) = name
conName (RecC name _) = name
conName (InfixC _ name _) = name
conName (ForallC _ _ con) = conName con
conArity :: (Num a) => Name -> Q a
conArity con = do
DataConI _ conType _ _ <- reify con
return (arity conType)
conArgTypes :: Con -> [Type]
conArgTypes (NormalC _ args) = map snd args
conArgTypes (RecC _ args) = map (\(_,_,ty) -> ty) args
conArgTypes (InfixC t1 _ t2) = [snd t1, snd t2]
conArgTypes (ForallC _ _ con) = conArgTypes con
typeCons :: Name -> Q [Con]
typeCons ty = do
TyConI (typeDec) <- reify ty
return (typeDecDataCons typeDec)
typeDecDataCons :: Dec -> [Con]
typeDecDataCons (DataD _ _ _ cons _) = cons
typeDecDataCons (NewtypeD _ _ _ con _) = [con]
typeDecDataCons (TySynD _ _ ty) = error "typeDecDataCons doesn't support type synonyms"
typeDecDataCons _ = error "typeDecDataCons: not a type"
foldClause :: ExpQ -> Name -> [Name] -> Int -> Con -> Int -> ClauseQ
foldClause self ty funcNames nCons con conN = do
let cName = conName con
conArity <- conArity cName
let funcName = funcNames !! conN
let funcE = varE funcName
conArgs <- replicateM conArity (newName "x")
let conArgsPs = map varP conArgs
let conArgsEs = map varE conArgs
let conArgP = conP cName conArgsPs
let addRecursion argType argE = case argType of
ConT x
| x == ty -> appE self argE
AppT (ConT x) _
| x == ty -> appE self argE
AppT (AppT (ConT x) _) _
| x == ty -> appE self argE
_ -> argE
let argTypes = conArgTypes con
let conArgsEsWithRecursion = zipWith addRecursion argTypes conArgsEs
let funArgsPs = map varP funcNames
let pats = funArgsPs ++ [conArgP]
let body = normalB (appsE (funcE : conArgsEsWithRecursion) )
clause pats body []
foldDec :: Name -> ExpQ -> Name -> [Name] -> [Con] -> DecQ
foldDec fName self ty funcNames cons = funD fName clauses
where
nCons = length cons
clauses = zipWith (foldClause self ty funcNames nCons) cons [0..]
fold :: Name -> ExpQ
fold ty = do
cons <- typeCons ty
fName <- newName "fold"
let fE = varE fName
let nCons = length cons
funcNames <- replicateM nCons (newName "f")
let self = appsE (fE : map varE funcNames)
let fn = foldDec fName self ty funcNames cons
letE [fn] fE