{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE CPP #-}
module Generics.Simplistic.Deep.TH
( unfoldFamilyInto
, deriveDeepFor
, deriveInstancesWith
) where
import Control.Monad.State
import Control.Arrow ((***))
import Language.Haskell.TH hiding (match)
import Language.Haskell.TH.Syntax hiding (lift)
import qualified Data.Set as S
import Generics.Simplistic.Deep
unfoldFamilyInto :: String -> Q Type -> Q [Dec]
unfoldFamilyInto n first = do
ty <- first >>= convertType
allTys <- S.toList <$> execStateT (process ty) S.empty
listStr <- [t| [String] |]
return [ SigD (mkName n) listStr
, FunD (mkName n) [Clause [] (NormalB $ mkExp allTys) []]
]
where
mkExp :: [STy] -> Exp
mkExp = ListE . map (LitE . StringL . show . ppr . trevnocType)
deriveDeepFor :: Name -> Name -> Q [Dec]
deriveDeepFor pr fam =
let qprim = return $ ConT pr
qfam = return $ ConT fam
in deriveInstancesWith (\t -> [t| Deep $(qprim) $(qfam) $(return t) |]) fam
deriveInstancesWith :: (Type -> Q Type)
-> Name
-> Q [Dec]
deriveInstancesWith f fam = do
tys <- getTypeLevelList fam
forM tys $ \ty -> do
instTy <- f ty
return $ InstanceD Nothing [] instTy []
getTypeLevelList :: Name -> Q [Type]
getTypeLevelList x = do
mtyDecl <- reifyDec x
case mtyDecl of
Nothing -> fail ("Not a type declaration: " ++ show (ppr x))
Just (TySynD _ _ ty) -> getTyLL ty
Just d -> fail ("Not a type-level list: " ++ show (ppr x) ++ show (ppr d))
where
getTyLL :: Type -> Q [Type]
getTyLL (SigT t _) = getTyLL t
getTyLL PromotedNilT = return []
getTyLL (AppT (AppT PromotedConsT a) as) = (a:) <$> getTyLL as
getTyLL t = fail ("Not a type-level list: " ++ show (ppr x) ++ "; " ++ show t)
process :: STy -> StateT (S.Set STy) Q ()
process ty = do
tys <- get
if ty `S.member` tys
then return ()
else do
let (tyHd , args) = styFlatten ty
case tyHd of
ConST tyName -> do
tyDecl <- lift (reifyDec tyName)
case tyDecl of
Just dec -> processDecl dec args
Nothing -> return ()
_ -> fail "Invalid type"
processDecl :: Dec -> [STy] -> StateT (S.Set STy) Q ()
processDecl (DataD _ tyName vars _ cons _) args = do
modify (S.insert (styApp tyName args))
let argVal = zip (map tyvarName vars) args
mapM_ (processCon argVal) cons
processDecl (NewtypeD _ tyName vars _ con _) args = do
modify (S.insert (styApp tyName args))
let argVal = zip (map tyvarName vars) args
processCon argVal con
processDecl (TySynD _ vars ty) args = do
sty <- convertType ty
let argVal = zip (map tyvarName vars) args
process (styReduce argVal sty)
processDecl _ _
= fail "unknown decl"
processCon :: [(Name , STy)] -> Con -> StateT (S.Set STy) Q ()
processCon argVal con = do
fields <- mapM (fmap (styReduce argVal) . convertType) (conType con)
mapM_ process fields
tyvarName :: TyVarBndr -> Name
tyvarName (PlainTV n) = n
tyvarName (KindedTV n _) = n
vbtyTy :: VarBangType -> Type
vbtyTy (_ , _ , t) = t
btyTy :: BangType -> Type
btyTy (_ , t) = t
conType :: Con -> [Type]
conType (NormalC _ btys) = map btyTy btys
conType (RecC _ vbtys) = map vbtyTy vbtys
conType (InfixC tyl _ tyr) = map btyTy [tyl , tyr]
conType (ForallC _ _ c) = conType c
conType (GadtC _ btys _) = map btyTy btys
conType (RecGadtC _ vbtys _) = map vbtyTy vbtys
data STy
= AppST STy STy
| VarST Name
| ConST Name
deriving (Eq , Show, Ord)
#if __GLASGOW_HASKELL__ >= 808
convertType :: (MonadFail m) => Type -> m STy
#else
convertType :: (Monad m) => Type -> m STy
#endif
convertType (AppT a b) = AppST <$> convertType a <*> convertType b
convertType (SigT t _) = convertType t
convertType (VarT n) = return (VarST n)
convertType (ConT n) = return (ConST n)
convertType (ParensT t) = convertType t
convertType ListT = return (ConST (mkName "[]"))
convertType (TupleT n) = return (ConST (mkName $ '(':replicate (n-1) ',' ++ ")"))
convertType t = fail ("convertType: Unsupported Type: " ++ show t)
trevnocType :: STy -> Type
trevnocType (AppST a b) = AppT (trevnocType a) (trevnocType b)
trevnocType (VarST n) = VarT n
trevnocType (ConST n)
| n == mkName "[]" = ListT
| isTupleN n = TupleT $ length (show n) - 1
| otherwise = ConT n
where isTupleN n0 = take 2 (show n0) == "(,"
stySubst :: STy -> Name -> STy -> STy
stySubst (AppST a b) m n = AppST (stySubst a m n) (stySubst b m n)
stySubst (ConST a) _ _ = ConST a
stySubst (VarST x) m n
| x == m = n
| otherwise = VarST x
styReduce :: [(Name , STy)] -> STy -> STy
styReduce parms t = foldr (\(n , m) ty -> stySubst ty n m) t parms
styFlatten :: STy -> (STy , [STy])
styFlatten (AppST a b) = id *** (++ [b]) $ styFlatten a
styFlatten sty = (sty , [])
styApp :: Name -> [STy] -> STy
styApp name args = go (ConST name) (reverse args)
where go t [] = t
go t (x:xs) = AppST (go t xs) x
reifyDec :: Name -> Q (Maybe Dec)
reifyDec name =
do info <- reify name
case info of TyConI dec -> return (Just dec)
_ -> return Nothing