{-# LANGUAGE TemplateHaskell, CPP, NamedFieldPuns #-} {- Holy crap this code is messy. -} module Data.Acid.TemplateHaskell where import Language.Haskell.TH import Language.Haskell.TH.Ppr import Language.Haskell.TH.ExpandSyns import Data.Acid.Core import Data.Acid.Common import Data.List ((\\), nub, delete) import Data.Maybe (mapMaybe) import Data.SafeCopy import Data.Typeable import Data.Char import Data.Monoid ((<>)) import Control.Applicative import Control.Monad import Control.Monad.State (MonadState) import Control.Monad.Reader (MonadReader) {-| Create the control structures required for acid states using Template Haskell. This code: @ myUpdate :: Argument -> Update State Result myUpdate arg = ... myQuery :: Argument -> Query State Result myQuery arg = ... $(makeAcidic ''State ['myUpdate, 'myQuery]) @ will make @State@ an instance of 'IsAcidic' and provide the following events: @ data MyUpdate = MyUpdate Argument data MyQuery = MyQuery Argument @ -} makeAcidic :: Name -> [Name] -> Q [Dec] makeAcidic stateName eventNames = do stateInfo <- reify stateName case stateInfo of TyConI tycon ->case tycon of #if MIN_VERSION_template_haskell(2,11,0) DataD _cxt _name tyvars _kind constructors _derivs #else DataD _cxt _name tyvars constructors _derivs #endif -> makeAcidic' eventNames stateName tyvars constructors #if MIN_VERSION_template_haskell(2,11,0) NewtypeD _cxt _name tyvars _kind constructor _derivs #else NewtypeD _cxt _name tyvars constructor _derivs #endif -> makeAcidic' eventNames stateName tyvars [constructor] TySynD _name tyvars _ty -> makeAcidic' eventNames stateName tyvars [] _ -> error "Data.Acid.TemplateHaskell: Unsupported state type. Only 'data', 'newtype' and 'type' are supported." _ -> error "Data.Acid.TemplateHaskell: Given state is not a type." makeAcidic' :: [Name] -> Name -> [TyVarBndr] -> [Con] -> Q [Dec] makeAcidic' eventNames stateName tyvars constructors = do events <- sequence [ makeEvent eventName | eventName <- eventNames ] acidic <- makeIsAcidic eventNames stateName tyvars constructors return $ acidic : concat events makeEvent :: Name -> Q [Dec] makeEvent eventName = do eventType <- getEventType eventName d <- makeEventDataType eventName eventType b <- makeSafeCopyInstance eventName eventType i <- makeMethodInstance eventName eventType e <- makeEventInstance eventName eventType return [d,b,i,e] getEventType :: Name -> Q Type getEventType eventName = do eventInfo <- reify eventName case eventInfo of #if MIN_VERSION_template_haskell(2,11,0) VarI _name eventType _decl #else VarI _name eventType _decl _fixity #endif -> expandSyns eventType _ -> error $ "Data.Acid.TemplateHaskell: Events must be functions: " ++ show eventName --instance (SafeCopy key, Typeable key, SafeCopy val, Typeable val) => IsAcidic State where -- acidEvents = [ UpdateEvent (\(MyUpdateEvent arg1 arg2 -> myUpdateEvent arg1 arg2) ] makeIsAcidic eventNames stateName tyvars constructors = do types <- mapM getEventType eventNames stateType' <- stateType let preds = [ ''SafeCopy, ''Typeable ] ty = appT (conT ''IsAcidic) stateType handlers = zipWith makeEventHandler eventNames types cxtFromEvents = nub $ concat $ zipWith (eventCxts stateType' tyvars) eventNames types cxts' <- mkCxtFromTyVars preds tyvars cxtFromEvents instanceD (return cxts') ty [ valD (varP 'acidEvents) (normalB (listE handlers)) [] ] where stateType = foldl appT (conT stateName) (map varT (allTyVarBndrNames tyvars)) -- | This function analyses an event function and extracts any -- additional class contexts which need to be added to the IsAcidic -- instance. -- -- For example, if we have: -- -- > data State a = ... -- -- > setState :: (Ord a) => a -> UpdateEvent (State a) () -- -- Then we need to generate an IsAcidic instance like: -- -- > instance (SafeCopy a, Typeable a, Ord a) => IsAcidic (State a) -- -- Note that we can only add constraints for type variables which -- appear in the State type. If we tried to do this: -- -- > setState :: (Ord a, Ord b) => a -> b -> UpdateEvent (State a) () -- -- We will get an ambigious type variable when trying to create the -- 'IsAcidic' instance, because there is no way to figure out what -- type 'b' should be. -- -- The tricky part of this code is that we need to unify the type -- variables. -- -- Let's say the user writes their code using 'b' instead of 'a': -- -- > setState :: (Ord b) => b -> UpdateEvent (State b) () -- -- In the 'IsAcidic' instance, we are still going to use 'a'. So we -- need to rename the variables in the context to match. -- -- The contexts returned by this function will have the variables renamed. -- -- Additionally, if the event uses MonadReader or MonadState it might look -- like this: -- -- > setState :: (MonadState x m, IsFoo x) => m () -- -- In this case we have to rename 'x' to the actual state we're going to -- use. This is done by 'renameState'. eventCxts :: Type -- ^ State type -> [TyVarBndr] -- ^ type variables that will be used for the State type in the IsAcidic instance -> Name -- ^ 'Name' of the event -> Type -- ^ 'Type' of the event -> [Pred] -- ^ extra context to add to 'IsAcidic' instance eventCxts targetStateType targetTyVars eventName eventType = let TypeAnalysis { context = cxt, stateType } = analyseType eventName eventType -- find the type variable names that this event is using -- for the State type eventTyVars = findTyVars stateType -- create a lookup table table = zip eventTyVars (map tyVarBndrName targetTyVars) in map (unify table) -- rename the type variables (renameState stateType targetStateType cxt) where -- | rename the type variables in a Pred unify :: [(Name, Name)] -> Pred -> Pred #if MIN_VERSION_template_haskell(2,10,0) unify table p = rename p table p -- in 2.10.0: type Pred = Type #else unify table p@(ClassP n tys) = ClassP n (map (rename p table) tys) unify table p@(EqualP a b) = EqualP (rename p table a) (rename p table b) #endif -- | rename the type variables in a Type rename :: Pred -> [(Name, Name)] -> Type -> Type rename pred table t@(ForallT tyvarbndrs cxt typ) = -- this is probably wrong? I don't think acid-state can really handle this type anyway.. ForallT (map renameTyVar tyvarbndrs) (map (unify table) cxt) (rename pred table typ) where renameTyVar (PlainTV name) = PlainTV (renameName pred table name) renameTyVar (KindedTV name k) = KindedTV (renameName pred table name) k rename pred table (VarT n) = VarT $ renameName pred table n rename pred table (AppT a b) = AppT (rename pred table a) (rename pred table b) rename pred table (SigT a k) = SigT (rename pred table a) k rename _ _ typ = typ -- | rename a 'Name' renameName :: Pred -> [(Name, Name)] -> Name -> Name renameName pred table n = case lookup n table of Nothing -> error $ unlines [ "Data.Acid.TemplateHaskell: " , "" , show $ ppr_sig eventName eventType , "" , "can not be used as an UpdateEvent because the class context: " , "" , pprint pred , "" , "contains a type variable which is not found in the state type: " , "" , pprint targetStateType , "" , "You may be able to fix this by providing a type signature that fixes these type variable(s)" ] (Just n') -> n' -- | See the end of comment for 'eventCxts'. renameState :: Type -> Type -> Cxt -> Cxt renameState tfrom tto cxt = map renamePred cxt where #if MIN_VERSION_template_haskell(2,10,0) renamePred p = renameType p -- in 2.10.0: type Pred = Type #else renamePred (ClassP n tys) = ClassP n (map renameType tys) renamePred (EqualP a b) = EqualP (renameType a) (renameType b) #endif renameType n | n == tfrom = tto renameType (AppT a b) = AppT (renameType a) (renameType b) renameType (SigT a k) = SigT (renameType a) k renameType typ = typ -- UpdateEvent (\(MyUpdateEvent arg1 arg2) -> myUpdateEvent arg1 arg2) makeEventHandler :: Name -> Type -> ExpQ makeEventHandler eventName eventType = do assertTyVarsOk vars <- replicateM (length args) (newName "arg") let lamClause = conP eventStructName [varP var | var <- vars ] conE constr `appE` lamE [lamClause] (foldl appE (varE eventName) (map varE vars)) where constr = if isUpdate then 'UpdateEvent else 'QueryEvent TypeAnalysis { tyvars, argumentTypes = args, stateType, isUpdate } = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs stateTypeTyVars = findTyVars stateType tyVarNames = map tyVarBndrName tyvars assertTyVarsOk = case tyVarNames \\ stateTypeTyVars of [] -> return () ns -> error $ "Data.Acid.TemplateHaskell: " <> unlines [show $ ppr_sig eventName eventType , "" , "can not be used as an UpdateEvent because it contains the type variables: " , "" , pprint ns , "" , "which do not appear in the state type:" , "" , pprint stateType ] --data MyUpdateEvent = MyUpdateEvent Arg1 Arg2 -- deriving (Typeable) makeEventDataType :: Name -> Type -> DecQ makeEventDataType eventName eventType = do let con = normalC eventStructName [ strictType notStrict (return arg) | arg <- args ] #if MIN_VERSION_template_haskell(2,12,0) cxt = [derivClause Nothing [conT ''Typeable]] #elif MIN_VERSION_template_haskell(2,11,0) cxt = mapM conT [''Typeable] #else cxt = [''Typeable] #endif case args of #if MIN_VERSION_template_haskell(2,11,0) [_] -> newtypeD (return []) eventStructName tyvars Nothing con cxt _ -> dataD (return []) eventStructName tyvars Nothing [con] cxt #else [_] -> newtypeD (return []) eventStructName tyvars con cxt _ -> dataD (return []) eventStructName tyvars [con] cxt #endif where TypeAnalysis { tyvars, argumentTypes = args } = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs -- instance (SafeCopy key, SafeCopy val) => SafeCopy (MyUpdateEvent key val) where -- put (MyUpdateEvent a b) = do put a; put b -- get = MyUpdateEvent <$> get <*> get makeSafeCopyInstance eventName eventType = do let preds = [ ''SafeCopy ] ty = AppT (ConT ''SafeCopy) (foldl AppT (ConT eventStructName) (map VarT (allTyVarBndrNames tyvars))) getBase = appE (varE 'return) (conE eventStructName) getArgs = foldl (\a b -> infixE (Just a) (varE '(<*>)) (Just (varE 'safeGet))) getBase args contained val = varE 'contain `appE` val putVars <- replicateM (length args) (newName "arg") let putClause = conP eventStructName [varP var | var <- putVars ] putExp = doE $ [ noBindS $ appE (varE 'safePut) (varE var) | var <- putVars ] ++ [ noBindS $ appE (varE 'return) (tupE []) ] instanceD (mkCxtFromTyVars preds tyvars context) (return ty) [ funD 'putCopy [clause [putClause] (normalB (contained putExp)) []] , valD (varP 'getCopy) (normalB (contained getArgs)) [] ] where TypeAnalysis { tyvars, context, argumentTypes = args } = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs mkCxtFromTyVars preds tyvars extraContext = cxt $ [ classP classPred [varT tyvar] | tyvar <- allTyVarBndrNames tyvars, classPred <- preds ] ++ map return extraContext {- instance (SafeCopy key, Typeable key ,SafeCopy val, Typeable val) => Method (MyUpdateEvent key val) where type MethodResult (MyUpdateEvent key val) = Return type MethodState (MyUpdateEvent key val) = State key val -} makeMethodInstance eventName eventType = do let preds = [ ''SafeCopy, ''Typeable ] ty = AppT (ConT ''Method) (foldl AppT (ConT eventStructName) (map VarT (allTyVarBndrNames tyvars))) structType = foldl appT (conT eventStructName) (map varT (allTyVarBndrNames tyvars)) instanceContext = cxt $ [ classP classPred [varT tyvar] | tyvar <- allTyVarBndrNames tyvars , classPred <- preds ] ++ map return context instanceD instanceContext (return ty) #if __GLASGOW_HASKELL__ >= 707 [ tySynInstD ''MethodResult (tySynEqn [structType] (return resultType)) , tySynInstD ''MethodState (tySynEqn [structType] (return stateType)) #else [ tySynInstD ''MethodResult [structType] (return resultType) , tySynInstD ''MethodState [structType] (return stateType) #endif ] where TypeAnalysis { tyvars, context, stateType, resultType } = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs --instance (SafeCopy key, Typeable key -- ,SafeCopy val, Typeable val) => UpdateEvent (MyUpdateEvent key val) makeEventInstance :: Name -> Type -> DecQ makeEventInstance eventName eventType = do let preds = [ ''SafeCopy, ''Typeable ] eventClass = if isUpdate then ''UpdateEvent else ''QueryEvent ty = AppT (ConT eventClass) (foldl AppT (ConT eventStructName) (map VarT (allTyVarBndrNames tyvars))) instanceD (cxt $ [ classP classPred [varT tyvar] | tyvar <- allTyVarBndrNames tyvars, classPred <- preds ] ++ map return context) (return ty) [] where TypeAnalysis { tyvars, context, isUpdate } = analyseType eventName eventType eventStructName = mkName (structName (nameBase eventName)) structName [] = [] structName (x:xs) = toUpper x : xs data TypeAnalysis = TypeAnalysis { tyvars :: [TyVarBndr] , context :: Cxt , argumentTypes :: [Type] , stateType :: Type , resultType :: Type , isUpdate :: Bool } deriving (Eq, Show) analyseType :: Name -> Type -> TypeAnalysis analyseType eventName t = go [] [] [] t where #if MIN_VERSION_template_haskell(2,10,0) getMonadReader :: Cxt -> Name -> [(Type, Type)] getMonadReader cxt m = do constraint@(AppT (AppT (ConT c) x) m') <- cxt guard (c == ''MonadReader && m' == VarT m) return (constraint, x) getMonadState :: Cxt -> Name -> [(Type, Type)] getMonadState cxt m = do constraint@(AppT (AppT (ConT c) x) m') <- cxt guard (c == ''MonadState && m' == VarT m) return (constraint, x) #else getMonadReader :: Cxt -> Name -> [(Pred, Type)] getMonadReader cxt m = do constraint@(ClassP c [x, m']) <- cxt guard (c == ''MonadReader && m' == VarT m) return (constraint, x) getMonadState :: Cxt -> Name -> [(Pred, Type)] getMonadState cxt m = do constraint@(ClassP c [x, m']) <- cxt guard (c == ''MonadState && m' == VarT m) return (constraint, x) #endif -- a -> b go tyvars cxt args (AppT (AppT ArrowT a) b) = go tyvars cxt (args ++ [a]) b -- Update st res -- Query st res go tyvars context argumentTypes (AppT (AppT (ConT con) stateType) resultType) | con == ''Update = TypeAnalysis { tyvars, context, argumentTypes, stateType, resultType , isUpdate = True } | con == ''Query = TypeAnalysis { tyvars, context, argumentTypes, stateType, resultType , isUpdate = False } -- (...) => a go tyvars cxt args (ForallT tyvars2 cxt2 a) = go (tyvars ++ tyvars2) (cxt ++ cxt2) args a -- (MonadState state m) => ... -> m result -- (MonadReader state m) => ... -> m result go tyvars' cxt argumentTypes (AppT (VarT m) resultType) | [] <- queries, [(cx, stateType)] <- updates = TypeAnalysis { tyvars, argumentTypes , stateType, resultType , isUpdate = True , context = delete cx cxt } | [(cx, stateType)] <- queries, [] <- updates = TypeAnalysis { tyvars, argumentTypes , stateType, resultType , isUpdate = False , context = delete cx cxt } where queries = getMonadReader cxt m updates = getMonadState cxt m tyvars = filter ((/= m) . tyVarBndrName) tyvars' -- otherwise, fail go _ _ _ _ = error $ "Data.Acid.TemplateHaskell: Event has an invalid type signature: Not an Update, Query, MonadState, or MonadReader: " ++ show eventName -- | find the type variables -- | e.g. State a b ==> [a,b] findTyVars :: Type -> [Name] findTyVars (ForallT _ _ a) = findTyVars a findTyVars (VarT n) = [n] findTyVars (AppT a b) = findTyVars a ++ findTyVars b findTyVars (SigT a _) = findTyVars a findTyVars _ = [] -- | extract the 'Name' from a 'TyVarBndr' tyVarBndrName :: TyVarBndr -> Name tyVarBndrName (PlainTV n) = n tyVarBndrName (KindedTV n _) = n allTyVarBndrNames :: [TyVarBndr] -> [Name] allTyVarBndrNames tyvars = map tyVarBndrName tyvars