packages feed

effet-0.4.0.0: src/Control/Effect/Machinery/TH.hs

{-# LANGUAGE CPP             #-}
{-# 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
  , makeUntagged
  , makeUntaggedWith
    -- * Lifting Convenience
  , liftL
  , runL
    -- * Naming Convention
  , removeApostrophe
  ) where

-- base
import Control.Monad          (forM, replicateM)
import Control.Monad.IO.Class (MonadIO)
import Data.Coerce            (coerce)
import Data.List              (isSuffixOf)
import Data.Maybe             (catMaybes, 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)

-----------------------------------------
-- Information about effect type classes.
-----------------------------------------
data EffectInfo = EffectInfo
  { effCxts    :: [Type]
  , effName    :: Name
  , effParams  :: [TyVarBndr]
  , effMonad   :: TyVarBndr
  , effMethods :: [Signature]
  }

data Signature = Signature
  { sigName :: Name
  , sigType :: Type
  }

-- Given a type class name, extracts infos about an effect.
effectInfo :: Name -> Q EffectInfo
effectInfo className = do
  info <- reify className
  case info of
    ClassI (ClassD cxts name tyVars _ decs) _ -> do
      (params, monad) <-
        case tyVars of
          [] -> fail
                  $  "The specified effect type class `"
                  ++ nameBase name
                  ++ "' has no monad type variable. "
                  ++ "It is expected to be the last type variable."
          vs -> pure (init vs, last vs)
      let sigs = [Signature n t | SigD n t <- decs]
      pure $ EffectInfo cxts name params monad sigs
    other ->
      fail
         $ "The specified name `"
        ++ nameBase className
        ++ "' is not a type class, but the following instead: "
        ++ show other

-- Constructs the type of an effect, i.e. the type class
-- without its monad parameter.
effectType :: EffectInfo -> Q Type
effectType info =
  foldl
    ( appT )
    ( conT $ effName info )
    ( fmap tyVarType (effParams info) )

-- Extracts the super classes of an effect which have the
-- kind of effects. As an example, for the following effect ...
--
-- class (State s m, Monad m) => MyEffect s m where ...
--
-- ... this would return [State s, Monad].
superEffects :: EffectInfo -> [Type]
superEffects info =
  catMaybes $ fmap extract (effCxts info)
    where
      m = tyVarName (effMonad info)
      extract = \case
        ForallT _ _ t -> extract t
        SigT t _      -> extract t
        ParensT t     -> extract t
        t `AppT` VarT n      | n == m -> Just t
        InfixT t _ (VarT n)  | n == m -> Just t
        UInfixT t _ (VarT n) | n == m -> Just t
#if __GLASGOW_HASKELL__ >= 808
        AppKindT t _ -> extract t
        ImplicitParamT _ t -> extract t
#endif
        _ -> Nothing

-- Like superEffects, but ignores super classes from base
-- (i.e., Applicative, Functor, Monad, MonadIO).
superEffectsWithoutBase :: EffectInfo -> [Type]
superEffectsWithoutBase =
  filter (not . isBase) . superEffects 
    where
      isBase = \case
        ConT n -> n `elem` [''Applicative, ''Functor, ''Monad, ''MonadIO]
        _ -> False

-------------------------------------------------
-- Tagging information about effect type classes.
-------------------------------------------------
data TaggedInfo = TaggedInfo
  { tgTag    :: TyVarBndr
  , tgParams :: [TyVarBndr]
  }

-- Given an effect, extracts infos about the tag parameter.
taggedInfo :: EffectInfo -> Q TaggedInfo
taggedInfo info =
  case effParams info of
    []     -> fail "The effect has no tag parameter."
    (v:vs) -> pure $ TaggedInfo v vs

-- | 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
  effInfo   <- effectInfo className
  hInstance <- handler effInfo
  fInstance <- finder effInfo
  lInstance <- lifter effInfo
  tInstance <- identityTaggerInstance effInfo
  pure [hInstance, fInstance, lInstance, tInstance]

-- | 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 mapping className = do
  let f = fmap mkName . mapping . nameBase
  effInfo <- effectInfo className
  tagInfo <- taggedInfo effInfo
  tagger f effInfo 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 mapping className = do
  let f = fmap mkName . mapping . nameBase
  effInfo    <- effectInfo className
  tagInfo    <- taggedInfo effInfo
  hInstance  <- handler effInfo
  fInstance  <- finder effInfo
  lInstance  <- lifter effInfo
  taggerDecs <- tagger f effInfo tagInfo
  pure (hInstance : fInstance : lInstance : taggerDecs)

-- | Given a list of function names, this function generates untagged versions
-- of them, i.e. it removes the tag type parameters  from their type signatures
-- (by applying 'G') and converts tagged effect type classes found in the
-- signature to their corresponding untagged type synonyms using 'removeApostrophe'.
--
-- @since 0.4.0.0
makeUntagged :: [Name] -> Q [Dec]
makeUntagged = makeUntaggedWith removeApostrophe

-- | Similar to 'makeUntagged', but allows to define a naming convention function
-- for the names of the generated functions and the effect type classes modified
-- in the type signatures.
--
-- The default naming convention is enforced by 'removeApostrophe', which simply
-- removes the apostrophe \"'\" at the end of a name.
--
-- @since 0.4.0.0
makeUntaggedWith :: (String -> Q String) -> [Name] -> Q [Dec]
makeUntaggedWith mapping names =
  let f = fmap mkName . mapping . nameBase in
  fmap concat $ forM names $ \name -> do
    info <- reify name
    case info of
      VarI funName typ _ -> do
        tag <- findTagParameter typ
        genName <- f funName
        funSig <- sigD genName $ replaceTag f tag typ
        funDef <- [d| $(varP genName) = $(varE funName) @G |]
        funInline <- pragInlD genName Inline FunLike AllPhases
        pure (funSig : funInline : funDef)
      other ->
        fail
           $ "Expected a function for name " ++ nameBase name
          ++ ", but encountered: " ++ show other

-- | Similar to 'makeEffect', but only generates the effect type class instance
-- for handling an effect.
makeHandler :: Name -> Q [Dec]
makeHandler className = do
  effInfo   <- effectInfo className
  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
  effInfo   <- effectInfo className
  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
  effInfo   <- effectInfo className
  lInstance <- lifter effInfo
  pure [lInstance]

tagger :: (Name -> Q Name) -> EffectInfo -> TaggedInfo -> Q [Dec]
tagger f effInfo tagInfo = do
  taggerFuns   <- taggerFunctions effInfo tagInfo
  untaggedSyn  <- untaggedSynonym f effInfo tagInfo
  untaggedFuns <- untaggedFunctions f effInfo tagInfo
  taggerInst   <- taggerInstance effInfo tagInfo
  pure
    $ untaggedSyn
    : taggerInst
    : taggerFuns
   ++ untaggedFuns

handler :: EffectInfo -> Q Dec
handler info = do
  funs   <- handlerFunctions info
  others <- newName "others"
  trafo  <- newName "t"
  instanceD
    ( instanceHandleCxt others trafo )
    ( instanceHead (promotedConsT `appT` effectType info `appT` varT others) trafo info )
    ( fmap pure funs )
  where
    instanceHandleCxt :: Name -> Name -> Q Cxt
    instanceHandleCxt others trafo = cxt
      [
        conT ''Handle
          `appT` typeLevelList (fmap pure $ superEffects info)
          `appT` effectType info
          `appT` varT others
          `appT` varT trafo
          `appT` tyVarType (effMonad info)
      ]

finder :: EffectInfo -> Q Dec
finder info = do
  funs  <- finderFunctions info
  other <- newName "other"
  effs  <- newName "effs"
  trafo <- newName "t"
  instanceWithOverlapD
    ( Just Overlappable )
    ( instanceFinderCxt other effs trafo )
    ( instanceHead (promotedConsT `appT` varT other `appT` varT effs) trafo info )
    ( fmap pure funs )
  where
    instanceFinderCxt :: Name -> Name -> Name -> Q Cxt
    instanceFinderCxt other effs trafo = cxt
      [
        conT ''Find
          `appT` typeLevelList (fmap pure $ superEffects info)
          `appT` effectType info
          `appT` varT other
          `appT` varT effs
          `appT` varT trafo
          `appT` tyVarType (effMonad info)
      ]

lifter :: EffectInfo -> Q Dec
lifter info = do
  let
    monad = effMonad info
    liftType =
      if any (isHigherOrder monad) (effMethods info)
      then ''Control
      else ''Lift
  funs  <- lifterFunctions info
  trafo <- newName "t"
  instanceD
    ( instanceLiftControlCxt liftType trafo )
    ( instanceHead promotedNilT trafo info )
    ( fmap pure funs )
  where
    instanceLiftControlCxt :: Name -> Name -> Q Cxt
    instanceLiftControlCxt name trafo = cxt
      [
        conT name
          `appT` typeLevelList (fmap pure $ superEffects info)
          `appT` effectType info
          `appT` varT trafo
          `appT` tyVarType (effMonad info)
      ]

instanceHead :: Q Type -> Name -> EffectInfo -> Q Type
instanceHead effs trafo info =
  effectType info
    `appT` (
      conT ''EachVia
        `appT` effs
        `appT` varT trafo
        `appT` tyVarType (effMonad info)
      )

taggerFunctions :: EffectInfo -> TaggedInfo -> Q [Dec]
taggerFunctions effInfo tagInfo = do
  let tagVar       = tgTag tagInfo
      nameString   = nameBase (effName effInfo)
      tagFName     = mkName ("tag"   ++ nameString)
      retagFName   = mkName ("retag" ++ nameString)
      untagFName   = mkName ("untag" ++ nameString)
  new    <- newName "new"
  tagF   <- taggerFunction tagFName effInfo tagInfo Nothing (Just new)
  retagF <- taggerFunction retagFName effInfo tagInfo (Just tagVar) (Just new)
  untagF <- taggerFunction untagFName effInfo tagInfo (Just tagVar) Nothing
  pure $
    tagF ++ retagF ++ untagF
  
taggerFunction :: Name -> EffectInfo -> TaggedInfo -> Maybe TyVarBndr -> Maybe Name -> Q [Dec]
taggerFunction funName effInfo tagInfo tag new = do
  mName <- newName "m"
  aName <- newName "a"
  gType <- [t| G |]
  let m           = varT mName
      a           = varT aName
      params      = tgParams tagInfo
      tagParam    = maybe (pure gType) (varT . tyVarName) tag
      newParam    = maybe (pure gType) varT new
      tagVars     = maybeToList tag ++ maybeToList (fmap PlainTV new)
      forallVars  = fmap unkindTyVar (tagVars ++ params) ++ [PlainTV mName, PlainTV aName]
      paramTypes  = fmap (tyVarType . unkindTyVar) params
      effType     = foldl appT (conT $ effName effInfo) (tagParam : paramTypes)
      effList     = effType : fmap pure (superEffectsWithoutBase effInfo)
      untagList =
        case tag of
            Nothing -> fmap (fmap (replace (tyVarName $ tgTag tagInfo) gType)) effList
            Just _  -> effList
      taggerType = [t| Tagger $tagParam $newParam |]
      viaType =
        case untagList of
#if __GLASGOW_HASKELL__ >= 808
          [e] -> uInfixT e ''Via taggerType
          es  -> uInfixT (typeLevelList es) ''EachVia taggerType
#else
          [e] -> conT ''Via `appT` e `appT` taggerType
          es  -> conT ''EachVia `appT` typeLevelList es `appT` taggerType
#endif
      funSigType = [t| $viaType $m $a -> $m $a |]
  funSig    <- sigD funName $ forallT forallVars (cxt []) funSigType
  funDef    <- [d| $(varP funName) = runTagger . runVia |]
  funInline <- pragInlD funName Inline FunLike AllPhases
  pure (funSig : funInline : funDef)
    where
      replace :: Name -> Type -> Type -> Type
      replace oldTag newTag = \case
        ConT n `AppT` VarT param | param == oldTag -> ConT n `AppT` newTag
        ForallT vars ctx t -> ForallT vars ctx (replace oldTag newTag t)
        AppT l r           -> AppT (replace oldTag newTag l) r
        SigT t k           -> SigT (replace oldTag newTag t) k
        InfixT l n r       -> InfixT (replace oldTag newTag l) n (replace oldTag newTag r)
        UInfixT l n r      -> UInfixT (replace oldTag newTag l) n (replace oldTag newTag r)
        ParensT t          -> ParensT (replace oldTag newTag t)
#if __GLASGOW_HASKELL__ >= 808
        AppKindT t k -> AppKindT (replace oldTag newTag t) k
        ImplicitParamT s t -> ImplicitParamT s (replace oldTag newTag t)
#endif
        other              -> other

untaggedSynonym :: (Name -> Q Name) -> EffectInfo -> TaggedInfo -> Q Dec
untaggedSynonym f effInfo tagInfo = do
  synName <- f (effName effInfo)
  tySynD
    ( synName )
    ( params  )
    ( foldl appT (conT $ effName effInfo) (conT ''G : fmap tyVarType params) )
  where
    params = fmap unkindTyVar (tgParams tagInfo)

untaggedFunctions :: (Name -> Q Name) -> EffectInfo -> TaggedInfo -> Q [Dec]
untaggedFunctions f effInfo tagInfo = do
  synName <- f (effName effInfo)
  fmap concat $
    forM (effMethods effInfo)
      $ untaggedFunction f
      $ foldl
          ( appT         )
          ( conT synName )
          ( fmap (tyVarType . unkindTyVar) $ tgParams tagInfo ++ [effMonad effInfo] )

untaggedFunction :: (Name -> Q Name) -> Q Type -> Signature -> Q [Dec]
untaggedFunction f effType sig = do
  let originalName = sigName sig
      signatureBody = pure (unkindType $ sigType sig)
  funName   <- f originalName
  funSig    <- sigD funName [t| $effType => $signatureBody |]
  funDef    <- [d| $(varP funName) = $(varE originalName) @G |]
  funInline <- pragInlD funName Inline FunLike AllPhases
  pure (funSig : funInline : funDef)

taggerInstance :: EffectInfo -> TaggedInfo -> Q Dec
taggerInstance effInfo tagInfo = do
  newTagName <- newName "new"
  let new = varT newTagName
      monadName = tyVarName (effMonad effInfo)
      m = varT monadName
      tag = tyVarType (tgTag tagInfo)
      effType = conT (effName effInfo)
      paramTypes = fmap tyVarType (tgParams tagInfo)
      taggerType = [t| Tagger $tag $new $m |]
      cxtParams  = new : paramTypes ++ [m]
      headParams = tag : paramTypes ++ [taggerType]
  funs <-
    fmap concat $
      forM (effMethods effInfo) $ taggerInstanceFunction new monadName
  instanceD
    ( cxt [foldl appT effType cxtParams] )
    ( foldl appT effType 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)

identityTaggerInstance :: EffectInfo -> Q Dec
identityTaggerInstance info = do
  oldTagName <- newName "tag"
  newTagName <- newName "new"
  let old = varT oldTagName
      new = varT newTagName
      monadName = tyVarName (effMonad info)
      m = varT monadName
      effType = conT $ effName info
      paramTypes = fmap tyVarType (effParams info)
      taggerType = [t| Tagger $old $new $m |]
      cxtParams  = paramTypes ++ [m]
      headParams = paramTypes ++ [taggerType]
  funs <-
    fmap concat $
      forM (effMethods info) $
        function [| Tagger |] [| runTagger |] (effMonad info) (effParams info)
  instanceD
    ( cxt [foldl appT effType cxtParams] )
    ( foldl appT effType headParams )
    ( fmap pure funs )

handlerFunctions :: EffectInfo -> Q [Dec]
handlerFunctions info =
  fmap concat $
    mapM
      ( function [| EachVia |] [| runVia |] (effMonad info) (effParams info) )
      ( effMethods info )

finderFunctions :: EffectInfo -> Q [Dec]
finderFunctions info =
  fmap concat $
    mapM
      ( function [| liftL |] [| runL |] (effMonad info) (effParams info) )
      ( effMethods info )

lifterFunctions :: EffectInfo -> Q [Dec]
lifterFunctions info =
  let m = effMonad info
      params = effParams info
      invalid = fail
         $ "Could not generate effect instance because the operation is "
        ++ "invalid for higher-order effects."
  in
  fmap concat $
    forM (effMethods 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")    
  resType <- 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 resType) (restoreT . pure)
        |]
  funDef    <- funD funName [clause (fmap varP fParams) (normalB body) []]
  funInline <- pragInlD funName Inline FunLike AllPhases
  pure [funDef, funInline]
  where
    restorables :: Bool -> Name -> Type -> [Type]
    restorables neg m = \case
      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
#if __GLASGOW_HASKELL__ >= 808
      AppKindT t _ -> restorables neg m t
      ImplicitParamT _ t -> restorables neg m t
#endif
      other -> fail
        $  "Encountered an unknown term when finding restorables: "
        ++ show other
    traverseExp :: Type -> Q Exp
    traverseExp = \case
      ForallT _ _ t -> traverseExp t
      AppT _ r      -> traverseRec r
      SigT t _      -> traverseExp t
      InfixT _ _ r  -> traverseRec r
      UInfixT _ _ r -> traverseRec r
      ParensT t     -> traverseExp t
#if __GLASGOW_HASKELL__ >= 808
      AppKindT t _ -> traverseExp t
      ImplicitParamT _ t -> traverseExp t
#endif
      _             -> [| id |]
      where
        traverseRec t = [| traverse . $(traverseExp t) |]

derive :: [Type] -> Q Exp -> Q Exp -> Name -> Type -> Q Exp
derive rs f inv m = \case
  -- TODO: This is missing some cases - see algorithm of DeriveFunctor.
  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
#if __GLASGOW_HASKELL__ >= 808
  AppKindT t _ ->
    derive rs f inv m t
  ImplicitParamT _ t ->
    derive rs f inv m t
#endif
  other -> fail
     $ "Could not generate effect instance because an unknown structure "
    ++ "was encountered: "
    ++ show other

---------------------
-- Utility functions.
---------------------

-- Throws away all kind information and forall from a type.
unkindType :: Type -> Type
unkindType = \case
  -- 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)
#if __GLASGOW_HASKELL__ >= 808
  AppKindT t _ -> unkindType t
  ImplicitParamT s t -> ImplicitParamT s (unkindType t)
#endif
  other         -> other

-- Throws away the kind information of a type variable.
unkindTyVar :: TyVarBndr -> TyVarBndr
unkindTyVar (KindedTV n _) = PlainTV n
unkindTyVar unkinded       = unkinded

-- Returns the name of a type variable.
tyVarName :: TyVarBndr -> Name
tyVarName (PlainTV  n  ) = n
tyVarName (KindedTV n _) = n

-- Converts a type variable to a type.
tyVarType :: TyVarBndr -> Q Type
tyVarType (PlainTV n   ) = varT n
tyVarType (KindedTV n k) = sigT (varT n) k

-- Counts the parameters of a type.
paramCount :: Type -> Int
paramCount = \case
  ArrowT `AppT` _ `AppT` r -> 1 + paramCount r
  ForallT _ _ t            -> paramCount t
  _                        -> 0

-- | 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 #-}

-- | 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 \"'\"."

-- Converts a list of types to a type-level list.
typeLevelList :: [Q Type] -> Q Type
typeLevelList []     = promotedNilT
typeLevelList (t:ts) = promotedConsT `appT` t `appT` typeLevelList ts

-- Returns the result type of a monadic type m.
-- Example: X -> Y -> Z -> m a
-- Returns: a
resultType :: Name -> Type -> Q Type
resultType m = \case
  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
#if __GLASGOW_HASKELL__ >= 808
  AppKindT t _ -> resultType m t
  ImplicitParamT _ t -> resultType m t
#endif
  other -> fail
    $  "Expected a return type of the form 'm a', but encountered: "
    ++ show other

-- Checks if a name m appears somewhere in a type.
contains :: Name -> Type -> Bool
contains m = \case
  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
#if __GLASGOW_HASKELL__ >= 808
  AppKindT t _ -> contains m t
  ImplicitParamT _ t -> contains m t
#endif
  _             -> False

-- Given a monad type variable m and a type, checks if the
-- type is a higher-order type where m is in negative position.
isHigherType :: TyVarBndr -> Type -> Bool
isHigherType monad = go False
  where
    m = tyVarName monad
    go negPos = \case
      VarT n `AppT` _ | n == m -> negPos
      ArrowT `AppT` a `AppT` r ->
        go (not negPos) a || go negPos r
      ForallT _ _ t ->
        go negPos t
      _ ->
        False

-- Given a monad type variable m and a signature, checks if its
-- type is a higher-order type where m is in negative position.
isHigherOrder :: TyVarBndr -> Signature -> Bool
isHigherOrder monad = isHigherType monad . sigType

-- Finds the first ("leftmost") type parameter of a type, which
-- is expected to be the tag type parameter.
findTagParameter :: Type -> Q Name
findTagParameter typ =
  case go typ of
    Just n -> pure n
    Nothing ->
      fail $ "Cannot find the tag parameter of the type: " ++ show typ
  where
    go :: Type -> Maybe Name
    go = \case
      ForallT tyVars ctx t ->
        case filter (not . isStar) tyVars of
          (v:_) -> Just $ tyVarName v
          [] ->
            case catMaybes (fmap go ctx) of
              (n:_) -> Just n
              [] -> go t
      AppT l r ->
        case go l of
          Just n  -> Just n
          Nothing -> go r
      SigT t _ -> go t
      VarT n -> Just n
      InfixT l _ r ->
        case go l of
          Just n  -> Just n
          Nothing -> go r
      UInfixT l _ r ->
        case go l of
          Just n  -> Just n
          Nothing -> go r
      ParensT t -> go t
#if __GLASGOW_HASKELL__ >= 808
      AppKindT t _ -> go t
      ImplicitParamT _ t -> go t
#endif
      _ -> Nothing
    -- We need this because the first type parameter
    -- is often 'k' for the kind of the tag. We ignore it.
    isStar :: TyVarBndr -> Bool
    isStar (PlainTV _) = True
    isStar (KindedTV _ StarT) = True
    isStar _ = False

-- Replaces the tag parameter with its G-counterpart, simplifying
-- types to their untagged synonym if possible.
replaceTag :: (Name -> Q Name) -> Name -> Type -> Q Type
replaceTag f tag = \case
  -- We eliminate outermost forall variables completely for now,
  -- to make the type signatures more readable.
  -- If we want to preserve it, we might need something
  -- like the line below.
  -- filter (not . (== tag) . tyVarName) tyVars
  ForallT _tyVars cxts t -> go (ForallT [] cxts t)
  other -> go other
  where
    go = \case
      ForallT tyVars cxts t ->
        forallT
          ( fmap unkindTyVar tyVars )
          ( sequence $ fmap go cxts )
          ( go t )
#if __GLASGOW_HASKELL__ >= 808
      ConT n `AppT` eff `AppT` t | n == ''Via || n == ''EachVia ->
        go (UInfixT eff n t)
#endif
      ConT n `AppT` VarT t | t == tag ->
        f n >>= conT
      AppT l r ->
        appT (go l) (go r)
      SigT t _ ->
        go t -- eliminate kinds for readability.
      VarT n | n == tag -> conT ''G
             | otherwise -> varT n
      InfixT l n r ->
        infixT (go l) n (go r)
      UInfixT l n r ->
        uInfixT (go l) n (go r)
      ParensT t ->
        parensT (go t)
#if __GLASGOW_HASKELL__ >= 808
      AppKindT t k ->
        appKindT (go t) (pure k)
      ImplicitParamT s t ->
        implicitParamT s (go t)
#endif
      other ->
        pure other