{-# LANGUAGE TemplateHaskell #-} ----------------------------------------------------------------------------- -- | -- Module : Control.Effect.Machinery.TH -- Copyright : (c) Michael Szvetits, 2020 -- License : BSD3 (see the file LICENSE) -- Maintainer : typedbyte@qualified.name -- Stability : stable -- Portability : portable -- -- This module provides @TemplateHaskell@ functions to generate the handling, -- lifting and tagging infrastructure for effect type classes. ----------------------------------------------------------------------------- module Control.Effect.Machinery.TH ( -- * Common Generators makeEffect , makeHandler , makeFinder , makeLifter -- * Tag-based Generators , makeTaggedEffect , makeTaggedEffectWith , makeTagger , makeTaggerWith -- * Lifting Convenience , liftL , runL -- * Naming Convention , removeApostrophe ) where -- base import Control.Monad (forM, replicateM) import Data.Coerce (coerce) import Data.List (isSuffixOf) import Data.Maybe (maybeToList) -- monad-control import Control.Monad.Trans.Control (liftWith, restoreT) -- template-haskell import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax hiding (Lift, lift) -- transformers import Control.Monad.Trans.Class (lift) import Control.Effect.Machinery.Tagger (Tagger(..), runTagger) import Control.Effect.Machinery.Via (Control, EachVia(..), Find, G, Handle, Lift, Via, runVia) data ClassInfo = ClassInfo { clsCxt :: Cxt , clsName :: Name , clsTyVars :: [TyVarBndr] , _clsFunDeps :: [FunDep] , clsDecs :: [Dec] } data EffectInfo = EffectInfo { _effCxt :: Cxt , effType :: Q Type , effParams :: [TyVarBndr] , effMonad :: TyVarBndr , effName :: Name , effTrafoName :: Name , effSigs :: [Signature] } data TaggedInfo = TaggedInfo { tgTag :: TyVarBndr , tgParams :: [TyVarBndr] , tgMonad :: TyVarBndr , tgEffName :: Name , tgNameMap :: String -> Q String , tgSigs :: [Signature] } data Signature = Signature { sigName :: Name , sigType :: Type } synonymName :: TaggedInfo -> Q Name synonymName info = mapName (tgNameMap info) (tgEffName info) resultType :: Name -> Type -> Q Type resultType m typ = case typ of VarT n `AppT` a | n == m -> pure a ArrowT `AppT` _ `AppT` r -> resultType m r ForallT _ _ t -> resultType m t SigT t _ -> resultType m t ParensT t -> resultType m t other -> fail $ "Expected a return type of the form 'm a', but encountered: " ++ show other restorables :: Bool -> Name -> Type -> [Type] restorables neg m typ = case typ of VarT n `AppT` a | n == m && neg -> [a] ArrowT `AppT` a `AppT` r -> restorables (not neg) m a ++ restorables neg m r ForallT _ _ t -> restorables neg m t SigT t _ -> restorables neg m t ParensT t -> restorables neg m t other -> fail $ "Encountered an unknown term when finding restorables: " ++ show other isHigherType :: TyVarBndr -> Type -> Bool isHigherType monad = go False where m = tyVarName monad go negPos typ = case typ of VarT n `AppT` _ | n == m -> negPos ArrowT `AppT` a `AppT` r -> go (not negPos) a || go negPos r ForallT _ _ t -> go negPos t _ -> False isHigherOrder :: TyVarBndr -> Signature -> Bool isHigherOrder monad = isHigherType monad . sigType signature :: Dec -> Q Signature signature dec = case dec of SigD name typ -> pure (Signature name typ) other -> fail $ "The generation of the effect handling machinery currently supports" ++ " only signatures, but encountered: " ++ show other unkindTyVar :: TyVarBndr -> TyVarBndr unkindTyVar (KindedTV n _) = PlainTV n unkindTyVar unkinded = unkinded tyVarName :: TyVarBndr -> Name tyVarName (PlainTV n ) = n tyVarName (KindedTV n _) = n tyVarType :: TyVarBndr -> Q Type tyVarType (PlainTV n ) = varT n tyVarType (KindedTV n k) = sigT (varT n) k effectVars :: ClassInfo -> Q ([TyVarBndr], TyVarBndr) effectVars info = case clsTyVars info of [] -> fail $ "The specified effect type class `" ++ nameBase (clsName info) ++ "' has no monad type variable. " ++ "It is expected to be the last type variable." vs -> pure (init vs, last vs) effectInfo :: ClassInfo -> Q EffectInfo effectInfo info = do (params, clsM) <- effectVars info t <- newName "t" sigs <- mapM signature (clsDecs info) pure $ EffectInfo ( clsCxt info ) ( foldl appT (conT $ clsName info) (fmap tyVarType params) ) ( params ) ( clsM ) ( clsName info ) ( t ) ( sigs ) extractTag :: [TyVarBndr] -> Q (TyVarBndr, [TyVarBndr]) extractTag [] = fail "The effect has no tag parameter." extractTag (v:vs) = pure (v, vs) -- | Extracts the untagged name from a name which is expected to end with \"\'\". -- In other words, this function removes the suffix \"\'\" from a given name, -- or fails otherwise. removeApostrophe :: String -> Q String removeApostrophe name = if "'" `isSuffixOf` name then pure $ init name else fail $ "Tagged effect and function names are expected to end with \"'\"." mapName :: (String -> Q String) -> Name -> Q Name mapName f = fmap mkName . f . nameBase taggedInfo :: (String -> Q String) -> EffectInfo -> Q TaggedInfo taggedInfo f info = do (tag, params) <- extractTag (effParams info) pure $ TaggedInfo ( tag ) ( params ) ( effMonad info ) ( effName info ) ( f ) ( effSigs info ) classInfo :: Name -> Q ClassInfo classInfo className = do info <- reify className case info of ClassI (ClassD context name tyVars funDeps decs) _ -> pure (ClassInfo context name tyVars funDeps decs) other -> fail $ "The specified name `" ++ nameBase className ++ "' is not a type class, but the following instead: " ++ show other instanceFinderCxt :: Name -> Name -> EffectInfo -> Q Cxt instanceFinderCxt name effs info = cxt [ conT name `appT` effType info `appT` varT effs `appT` varT (effTrafoName info) `appT` tyVarType (effMonad info) ] instanceCxt :: Name -> EffectInfo -> Q Cxt instanceCxt name info = cxt [ conT name `appT` effType info `appT` varT (effTrafoName info) `appT` tyVarType (effMonad info) ] instanceHead :: Q Type -> EffectInfo -> Q Type instanceHead effs info = effType info `appT` ( conT ''EachVia `appT` effs `appT` varT (effTrafoName info) `appT` tyVarType (effMonad info) ) -- | Generates the effect handling and lifting infrastructure for an effect which -- does not have a tag type parameter. Requires the @TemplateHaskell@ language -- extension. -- -- Consider the following effect type class: -- -- @ -- class 'Monad' m => MyEffect a b c m where -- ... -- @ -- -- @makeEffect ''MyEffect@ then generates three instances for this effect type -- class ('Lift' for first-order effects, 'Control' for higher-order effects): -- -- @ -- instance 'Handle' (MyEffect a b c) t m => MyEffect a b c ('EachVia' (MyEffect a b c : effs) t m) where -- ... -- -- instance {-\# OVERLAPPABLE \#-} 'Find' (MyEffect a b c) effs t m => MyEffect a b c ('EachVia' (other : effs) t m) where -- ... -- -- instance 'Lift'/'Control' (MyEffect a b c) t m => MyEffect a b c ('EachVia' \'[] t m) where -- ... -- @ -- -- The first instance indicates that @MyEffect@ was found at the head of the type -- level list of effects to be handled, so @MyEffect@ is delegated to @t@. -- -- The second instance indicates that @MyEffect@ was not found at the head of the -- type level list of effects to be handled, so we must find @MyEffect@ in the tail @effs@ -- of the type level list. -- -- The third instance indicates that @MyEffect@ could not be found in the type level -- list of effects to be handled, so the effect must be delegated further down the monad -- transformer stack in order to find its corresponding effect handler. -- -- Without @TemplateHaskell@, you have to write these three instances by hand. These -- instances can also be generated separately, see 'makeHandler', 'makeFinder' and -- 'makeLifter'. makeEffect :: Name -> Q [Dec] makeEffect className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo hInstance <- handler effInfo fInstance <- finder effInfo lInstance <- lifter effInfo pure [hInstance, fInstance, lInstance] -- | Similar to 'makeTaggedEffect', but only generates the tag-related definitions. makeTagger :: Name -> Q [Dec] makeTagger = makeTaggerWith removeApostrophe -- | Similar to 'makeTaggedEffectWith', but only generates the tag-related definitions. makeTaggerWith :: (String -> Q String) -> Name -> Q [Dec] makeTaggerWith f className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo tagInfo <- taggedInfo f effInfo tagger tagInfo -- | Generates the effect handling and lifting infrastructure for an effect which -- has a tag type parameter. It is expected that the tag type parameter is the first -- type parameter of the effect type class. It is also expected that the names of the -- effect type class and its methods end with an apostrophe \"'\". If you want more -- control over the naming convention, use 'makeTaggedEffectWith'. -- -- In general, this function generates everything that 'makeEffect' does, but also some -- additional things. Consider the following effect type class: -- -- @ -- class 'Monad' m => MyEffect' tag a b c m where -- methodA' :: a -> m () -- methodB' :: b -> m () -- methodC' :: c -> m () -- @ -- -- @'makeTaggedEffect' \'\'MyEffect'@ then generates the following additional things: -- -- * A type synonym for the untagged version of @MyEffect'@ with the name @MyEffect@ -- (note the missing apostrophe). -- * Untagged versions of the effect methods, namely @methodA@, @methodB@ and @methodC@ -- (note the missing apostrophes). -- * An instance of @MyEffect'@ for the type 'Tagger' which does not handle the effect, -- but simply tags, retags or untags the @MyEffect'@ constraint of a computation. -- * Three functions @tagMyEffect'@, @retagMyEffect'@ and @untagMyEffect'@ which make -- use of the 'Tagger' instance. -- -- As a rule of thumb, whenever you see an apostrophe suffix in the name of a definition -- somewhere in this library, it has something to do with tags. Most of the time you -- will use such definitions in combination with the language extension @TypeApplications@, -- like: -- -- @ -- ... forall tag ... methodA' @tag ... -- tagMyEffect' \@\"newTag\" program -- retagMyEffect' \@\"oldTag\" \@\"newTag\" program -- untagMyEffect' \@\"erasedTag\" program -- @ -- -- All the tag-related definitions can also be generated separately (i.e., without the -- instances generated by 'makeEffect'), see 'makeTagger' and 'makeTaggerWith'. makeTaggedEffect :: Name -> Q [Dec] makeTaggedEffect = makeTaggedEffectWith removeApostrophe -- | Similar to 'makeTaggedEffect', but allows to define a naming convention function -- for the names of the effect type class and its methods. This function is used to -- transform the name of a tagged definition (i.e., the type class or its methods) into -- its untagged counterpart. -- -- The default naming convention is enforced by 'removeApostrophe', which simply -- removes the apostrophe \"'\" at the end of a name. makeTaggedEffectWith :: (String -> Q String) -> Name -> Q [Dec] makeTaggedEffectWith f className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo tagInfo <- taggedInfo f effInfo hInstance <- handler effInfo fInstance <- finder effInfo lInstance <- lifter effInfo taggerDecs <- tagger tagInfo pure (hInstance : fInstance : lInstance : taggerDecs) -- | Similar to 'makeEffect', but only generates the effect type class instance -- for handling an effect. makeHandler :: Name -> Q [Dec] makeHandler className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo hInstance <- handler effInfo pure [hInstance] -- | Similar to 'makeEffect', but only generates the effect type class instance -- for finding the effect in the tail of the type level list. -- -- @since 0.2.0.0 makeFinder :: Name -> Q [Dec] makeFinder className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo fInstance <- finder effInfo pure [fInstance] -- | Similar to 'makeEffect', but only generates the effect type class instance -- for lifting an effect. makeLifter :: Name -> Q [Dec] makeLifter className = do clsInfo <- classInfo className effInfo <- effectInfo clsInfo lInstance <- lifter effInfo pure [lInstance] tagger :: TaggedInfo -> Q [Dec] tagger info = do taggerFuns <- taggerFunctions info untaggedSyn <- untaggedSynonym info untaggedFuns <- untaggedFunctions info taggerInst <- taggerInstance info pure $ untaggedSyn : taggerInst : taggerFuns ++ untaggedFuns handler :: EffectInfo -> Q Dec handler info = do funs <- handlerFunctions info effs <- newName "effs" instanceD ( instanceCxt ''Handle info ) ( instanceHead (promotedConsT `appT` effType info `appT` varT effs) info ) ( fmap pure funs ) finder :: EffectInfo -> Q Dec finder info = do funs <- finderFunctions info other <- newName "other" effs <- newName "effs" instanceWithOverlapD ( Just Overlappable ) ( instanceFinderCxt ''Find effs info ) ( instanceHead (promotedConsT `appT` varT other `appT` varT effs) info ) ( fmap pure funs ) lifter :: EffectInfo -> Q Dec lifter info = do let monad = effMonad info context = if any (isHigherOrder monad) (effSigs info) then ''Control else ''Lift funs <- lifterFunctions info instanceD ( instanceCxt context info ) ( instanceHead promotedNilT info ) ( fmap pure funs ) taggerFunctions :: TaggedInfo -> Q [Dec] taggerFunctions info = do let params = tgParams info tagVar = tgTag info effectName = tgEffName info nameString = nameBase effectName tagFName = mkName ("tag" ++ nameString) retagFName = mkName ("retag" ++ nameString) untagFName = mkName ("untag" ++ nameString) tag <- newName (nameBase $ tyVarName tagVar) new <- newName "new" tagF <- taggerFunction effectName tagFName Nothing (Just new) params retagF <- taggerFunction effectName retagFName (Just tag) (Just new) params untagF <- taggerFunction effectName untagFName (Just tag) Nothing params pure $ tagF ++ retagF ++ untagF taggerFunction :: Name -> Name -> Maybe Name -> Maybe Name -> [TyVarBndr] -> Q [Dec] taggerFunction baseName funName tag new params = do mName <- newName "m" aName <- newName "a" let m = varT mName a = varT aName tagParam = maybe [t| G |] varT tag newParam = maybe [t| G |] varT new tagNames = maybeToList tag ++ maybeToList new paramNames = fmap tyVarName params paramTypes = fmap (tyVarType . unkindTyVar) params forallNames = tagNames ++ paramNames ++ [mName, aName] forallTypes = fmap PlainTV forallNames effectType = foldl appT (conT baseName) (tagParam : paramTypes) funSigType <- [t| ($effectType `Via` Tagger $tagParam $newParam) $m $a -> $m $a |] funSig <- sigD funName $ forallT forallTypes (cxt []) (pure funSigType) funDef <- [d| $(varP funName) = runTagger . runVia |] funInline <- pragInlD funName Inline FunLike AllPhases pure (funSig : funInline : funDef) untaggedSynonym :: TaggedInfo -> Q Dec untaggedSynonym info = do synName <- synonymName info tySynD ( synName ) ( params ) ( foldl appT (conT effectName) (conT ''G : fmap tyVarType params) ) where effectName = tgEffName info params = fmap unkindTyVar (tgParams info) untaggedFunctions :: TaggedInfo -> Q [Dec] untaggedFunctions info = do synName <- synonymName info fmap concat $ forM (tgSigs info) $ untaggedFunction (tgNameMap info) $ foldl ( appT ) ( conT synName ) ( fmap (tyVarType . unkindTyVar) $ tgParams info ++ [tgMonad info] ) untaggedFunction :: (String -> Q String) -> Q Type -> Signature -> Q [Dec] untaggedFunction f effectType sig = do let originalName = sigName sig signatureBody = pure (unkindType $ sigType sig) funName <- mapName f originalName funSig <- sigD funName [t| $effectType => $signatureBody |] funDef <- [d| $(varP funName) = $(varE originalName) @G |] funInline <- pragInlD funName Inline FunLike AllPhases pure (funSig : funInline : funDef) taggerInstance :: TaggedInfo -> Q Dec taggerInstance info = do newTagName <- newName "new" let new = varT newTagName monadName = tyVarName (tgMonad info) m = varT monadName tag = tyVarType (tgTag info) effectType = conT $ tgEffName info paramTypes = fmap tyVarType (tgParams info) taggerType = [t| Tagger $tag $new $m |] cxtParams = new : paramTypes ++ [m] headParams = tag : paramTypes ++ [taggerType] funs <- fmap concat $ forM (tgSigs info) $ taggerInstanceFunction new monadName instanceD ( cxt [foldl appT effectType cxtParams] ) ( foldl appT effectType headParams ) ( fmap pure funs ) taggerInstanceFunction :: Q Type -> Name -> Signature -> Q [Dec] taggerInstanceFunction new monad sig = do let typ = sigType sig funName = sigName sig expr = derive [] [| Tagger |] [| runTagger |] monad typ typeAppliedName = varE funName `appTypeE` new funDef <- [d| $(varP funName) = $expr $typeAppliedName |] funInline <- pragInlD funName Inline FunLike AllPhases pure (funInline : funDef) paramCount :: Type -> Int paramCount typ = case typ of ArrowT `AppT` _ `AppT` r -> 1 + paramCount r ForallT _ _ t -> paramCount t _ -> 0 invalid :: Q Exp invalid = fail $ "Could not generate effect instance because the operation is " ++ "invalid for higher-order effects." handlerFunctions :: EffectInfo -> Q [Dec] handlerFunctions info = fmap concat $ mapM ( function [| EachVia |] [| runVia |] (effMonad info) (effParams info) ) ( effSigs info ) -- | Adds an effect @eff@ to the type level list of effects that need to be -- handled by the transformer @t@. From a structural point of view, this is -- analogous to @lift@ in the @mtl@ ecosystem. This function comes in handy -- when writing the 'Find'-based instance of an effect by hand. -- -- @since 0.2.0.0 liftL :: EachVia effs t m a -> EachVia (eff : effs) t m a liftL = coerce {-# INLINE liftL #-} -- | Removes an effect @eff@ from the type level list of effects that need to be -- handled by the transformer @t@. From a structural point of view, this is -- analogous to the @run...@ functions in the @mtl@ ecosystem. This function -- comes in handy when writing the 'Find'-based instance of an effect by hand. -- -- @since 0.2.0.0 runL :: EachVia (eff : effs) t m a -> EachVia effs t m a runL = coerce {-# INLINE runL #-} finderFunctions :: EffectInfo -> Q [Dec] finderFunctions info = fmap concat $ mapM ( function [| liftL |] [| runL |] (effMonad info) (effParams info) ) ( effSigs info ) lifterFunctions :: EffectInfo -> Q [Dec] lifterFunctions info = let m = effMonad info params = effParams info in fmap concat $ forM (effSigs info) $ \sig -> if isHigherOrder m sig then higherFunction m params sig else function [| lift |] invalid m params sig function :: Q Exp -> Q Exp -> TyVarBndr -> [TyVarBndr] -> Signature -> Q [Dec] function f inv monad params sig = do let m = tyVarName monad funName = sigName sig paramTypes = fmap tyVarType params typeAppliedName = foldl appTypeE (varE funName) paramTypes expr = derive [] f inv m (sigType sig) funDef <- [d| $(varP funName) = $expr $typeAppliedName |] funInline <- pragInlD funName Inline FunLike AllPhases pure (funInline : funDef) higherFunction :: TyVarBndr -> [TyVarBndr] -> Signature -> Q [Dec] higherFunction monad params sig = do let m = tyVarName monad typ = sigType sig funName = sigName sig paramTypes = fmap tyVarType params restores = restorables False m typ expr = derive restores [| id |] [| run . runVia |] m typ fParams <- replicateM (paramCount typ) (newName "x") res <- resultType m typ let typeAppliedName = foldl appTypeE (varE funName) paramTypes appliedExp = foldl appE expr (typeAppliedName : fmap varE fParams) body = [| EachVia $ (liftWith $ \ $([p|run|]) -> $appliedExp) >>= $(traverseExp res) (restoreT . pure) |] funDef <- funD funName [clause (fmap varP fParams) (normalB body) []] funInline <- pragInlD funName Inline FunLike AllPhases pure [funDef, funInline] unkindType :: Type -> Type unkindType typ = case typ of -- We could need the following line if we want to preserve foralls --ForallT vs ps t -> ForallT (fmap unkindTyVar vs) (fmap unkindType ps) (unkindType t) ForallT _ _ t -> unkindType t AppT l r -> AppT (unkindType l) (unkindType r) SigT t _ -> t InfixT l n r -> InfixT (unkindType l) n (unkindType r) UInfixT l n r -> UInfixT (unkindType l) n (unkindType r) ParensT t -> ParensT (unkindType t) other -> other contains :: Name -> Type -> Bool contains m typ = case typ of ForallT _ _ t -> contains m t AppT l r -> contains m l || contains m r SigT t _ -> contains m t VarT n -> n == m ConT n -> n == m PromotedT n -> n == m InfixT l n r -> n == m || contains m l || contains m r UInfixT l n r -> n == m || contains m l || contains m r ParensT t -> contains m t _ -> False derive :: [Type] -> Q Exp -> Q Exp -> Name -> Type -> Q Exp derive rs f inv m typ = -- TODO: This is missing some cases - see algorithm of DeriveFunctor. case typ of t | not (contains m t) -> [| id |] VarT n `AppT` _ | n == m -> f ArrowT `AppT` arg `AppT` res -> let rf = derive rs f inv m res af = derive rs inv f m arg in if elem arg rs then [| \x b -> $rf (((x =<<) . EachVia . restoreT . pure) b) |] else [| \x b -> $rf (x ($af b)) |] ForallT _ _ t -> derive rs f inv m t other -> fail $ "Could not generate effect instance because an unknown structure " ++ "was encountered: " ++ show other traverseExp :: Type -> Q Exp traverseExp typ = case typ of ForallT _ _ t -> traverseExp t AppT _ r -> traverseRec r SigT t _ -> traverseExp t InfixT _ _ r -> traverseRec r UInfixT _ _ r -> traverseRec r ParensT t -> traverseExp t _ -> [| id |] where traverseRec t = [| traverse . $(traverseExp t) |]